【Vue.js入門】リアクティブの基礎を徹底解説!ref・reactive・computed・watchの使い方

Vue.js リアクティブの基礎を徹底解説

リアクティブの基礎、ある意味でここが本丸だと思います。
Vue.jsの核となる仕組みが リアクティブシステム です。
リアクティブを理解すれば、Vueが「なぜ直感的に使えるのか」が腑に落ち、実務での活用力が大きく高まります。……そのはずです。
※ 学習用に調べた内容を個人的にわかりやすい形にまとめました。文章や内容はAI補助もらいながら作っています。
👉 ※ 本記事にはアフィリエイトリンクが含まれています。

この記事では、Vue 3 の Composition API を前提に、以下の4つを解説します。

  • ref:単一値をリアクティブ化する「箱」
  • reactive:オブジェクト/配列を丸ごとリアクティブ化
  • computed:依存から自動計算される「派生値」
  • watch:変化を契機に「副作用」を実行

※ 上記の ref / reactive / computed / watch は、Vueの「Composition API」に含まれる リアクティブAPI(Reactivity APIs) です。『変数』そのものではなく、リアクティブな状態を作る・扱うための関数(API)だと捉えるのが正確です。個人的には ref と reactive がちょっと理解しにくかったです。

リアクティブシステムとは?

Vueは「データの変化に応じてDOMが『自動』で更新される」仕組みを持っています。
感覚的には “スプレッドシート” に近いです。セルAの値が変わると、セルAを参照しているセルBやグラフが自動で更新されますよね。Vueのリアクティブはまさにそれで、“データ=A列、画面=グラフ” と考えるとイメージしやすいはず。

従来のJavaScriptでは次のようなDOMを更新する処理を書かなければなりませんでした。


const el = document.querySelector("#text")
let message = "こんにちは"
el.textContent = message

// 値を変える(こんにちは 👉 こんばんは)たびにDOMを操作する必要がある
message = "こんばんは"
el.textContent = message

Vueでは、状態をリアクティブに定義すれば、DOM更新は自動で行われます。
つまり 「値の正しさ」に集中でき、DOM操作という“作業”をVueに委譲 できます。

※ DOM(Document Object Model): ブラウザがHTMLを読み込んで構築する画面要素のツリー状オブジェクト表現。JavaScriptから要素の取得・追加・削除・属性/テキスト変更やイベント処理ができ、画面の見た目はこのDOMをもとに描画される(=HTMLの生テキストそのものではなく、操作可能なデータ構造)。

ref:単一値をリアクティブ化する「箱」

ref は単一の値(文字列、数値、真偽値など)をリアクティブにするために使います。
“宝箱(ref)に値を入れて持ち運ぶ” と考えると分かりやすいです。テンプレートでは箱のフタを自動で開けて中身(値)を見せてくれますが、スクリプトの中では自分でフタ(.value)を開けます。


<template>
  <h2>{{ count }}</h2>
  <button @click="count++">カウントアップ</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
</script>

※ 拡張子は .vue です。

  • テンプレート内では count と書くだけ でOK(中身が自動的に取り出される)。
  • スクリプト内では count.value(宝箱のフタを開けるイメージ)。
  • ref(0) がオブジェクト化され、値が変わるたびにVueが依存関係をたどってDOMを自動更新 します。

もう少し実務寄りの例:トグル(ON/OFF)
UIの表示・非表示、ボタンの活性・非活性などで頻出するんじゃないかと思います。


<template>
  <button @click="show = !show">
    {{ show ? '隠す' : '表示する' }}
  </button>
  <p v-if="show">ここに詳細情報を表示します</p>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const show = ref(false) // 初期は非表示
</script>

よくあるつまずきポイント(ref編)

  • count = 1 と代入したのに反映されない」→ スクリプト内では count.value = 1 が正解。……めちゃめちゃつまづきそうですよね。筆者はまんまと……。
  • 「テンプレートで count++ と書くのはOK?」→ OK(テンプレート側は自動的に .value を扱う)。
  • 「オブジェクトも ref に入れて良い?」→ OK。ただし state.value.foo のように .value を経由します(シンプルにいきたいならオブジェクトは後述の reactive が楽)。

reactive:オブジェクト/配列を丸ごとリアクティブ化

reactive はオブジェクトや配列をまるごと リアクティブにします。
“家(オブジェクト)に電気(リアクティブ)を配線する” イメージ。廊下の電気も台所の電気も、同じ配電盤で一括管理できます。


<template>
  <p>名前: {{ user.name }}</p>
  <p>年齢: {{ user.age }}</p>
  <button @click="user.age++">年齢+1</button>
</template>

<script setup lang="ts">
import { reactive } from 'vue'

const user = reactive({
  name: '太郎',
  age: 20
})
</script>
  • ref が単一値なのに対し、reactive複数プロパティを束ねる のに向いています。
  • 配列 もOK。todos.push(...) といった通常の配列操作で反映されます。

実務寄りの配列例:


<template>
  <ul>
    <li v-for="(t, i) in todos" :key="i">{{ t }}</li>
  </ul>
  <button @click="addTodo">追加</button>
</template>

<script setup lang="ts">
import { reactive } from 'vue'

const state = reactive({
  todos: ['牛乳を買う']
})

function addTodo() {
  state.todos.push('卵を買う')
}
</script>

注意点(reactive編)

  • 分割代入の罠
    const { name } = user
    // 以後 name はただの文字列。user.name を更新しても name は変わらない
    
    初めのうちは 分割代入を避ける のが安全です(user.name のまま参照する)。
    • 分割代入: リアクティブ性が切れてただの値になる(以後の変更は伝播しない)。
      • 分割代入は「基本NG」。意図して“スナップショット(静的コピー)”が欲しいときだけ使う、が実務の定石
  • 入れ子もOKuser.profile.address.city のような深いプロパティ更新も追跡されます。
  • 使いどころ:フォームのまとまった入力、一覧の状態(フィルタ条件+結果配列など)を 一つの“状態の塊”として扱いたいとき に便利。

computed:依存から自動計算される「派生値」

computed は「他の状態から計算して得られる 値」を定義します。
“常に最新の見積金額や在庫数を表示する電卓” だと思ってください。元データが変わると、自動的に再計算されます。


<template>
  <p>フルネーム: {{ fullName }}</p>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

const firstName = ref('山田')
const lastName = ref('太郎')

// getter関数で算出プロパティを定義(キャッシュされる)
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
</script>
  • computed依存先が変わったときだけ再計算(=無駄な再計算を避ける=パフォーマンス良)。
  • テンプレートのロジックを computed に寄せると HTMLが読みやすく保てる

もう1ステップ実務寄り:入力と整形の分離
たとえば価格のフォーマット。


<template>
  <p>税込価格: {{ priceWithTax }}</p>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

const price = ref(1000)
const taxRate = ref(0.1)

// 小数→整数化などの表示責務をcomputedに寄せる
const priceWithTax = computed(() => Math.round(price.value * (1 + taxRate.value)) + '円')
</script>

ちょい応用(理解の助けになる程度!?)

  • computed は基本 読み取り専用。必要に応じて getter/setter で双方向も作れます(例:全角/半角変換など)。初学段階では 読み取り専用で十分 です。
  • 「値そのものを作りたいなら computed、値の変化に合わせて処理をしたいなら watch」と覚えると迷いません。

watch:変化を契機に「副作用」を実行

watch は特定のデータが変わったときに**任意の処理(副作用)**を行います。
“見張り役” のイメージ。ドアが開いたらアラームを鳴らす、みたいな役割です。


<template>
  <input v-model="keyword" placeholder="検索ワードを入力" />
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'

const keyword = ref('')

// keywordが変わるたびに処理を実行
watch(keyword, (newVal, oldVal) => {
  console.log(`検索ワードが ${oldVal} から ${newVal} に変わりました`)
})
</script>
  • APIリクエスト、ログ送信、ローカルストレージ保存など、副作用的な処理に最適
  • 「状態の変化に応じたアクション」を書きたいときに便利です。

もう少し実務寄り:“連続タイプ対策” の簡易ディレイ
(本格的なデバウンスは別のテーマなので、ここでは最小限のイメージだけ)


<script setup lang="ts">
import { ref, watch } from 'vue'

const keyword = ref('')
let timer: number | undefined

watch(keyword, (q) => {
  // タイピングのたびに前のタイマーをクリア→最後の入力から300ms後に実行
  if (timer) window.clearTimeout(timer)
  timer = window.setTimeout(() => {
    console.log('検索APIを呼ぶ想定:', q)
  }, 300)
})
</script>

よくあるつまずきポイント(watch編)

  • とりあえず一回実行したいwatch(source, cb, { immediate: true })
  • オブジェクトまるごと監視watch(obj, cb, { deep: true })(まずは単一の ref を監視できるようになるのが先)
  • 使い分けの芯表示用の計算=computed、外部とのやりとり=watch と捉えると整理できます。

図解:リアクティブの仕組み

Vueのリアクティブシステムは、以下のような流れで動作します。

リアクティブの基礎、vue.jsのリアクティブの仕組み
リアクティブの基礎、vue.jsのリアクティブの仕組み

個人的によく湧き上がる疑問

Q1. ref と reactive はどう使い分ける?

  • 単一の値なら ref。例:カウンタ、フラグ、1つの入力値。
  • オブジェクト/配列なら reactive。例:フォーム一式、フィルタ条件+結果配列。
    ※ 迷ったらまず ref を使うとシンプルです。

Q2. watch と computed の違いは?

  • computed値そのものを派生させたいとき(表示用の“最新の計算結果”)。
  • watch:値が変わったことをトリガーに処理(副作用)をしたいとき(API、ログ、保存など)。

Q3. テンプレートで .value が要らないのはなぜ?

  • テンプレートは ref自動で“中身”に展開してくれるためです。スクリプト内は明示的に .value が必要。

Q4. reactive を分割代入したら更新が伝わらない…

  • 分割代入した時点でただの値のコピーになります。初めのうちは user.name のように オブジェクト越しに参照 するのが安全です(=分割代入を避ける)。

👉 まとめ

  • Vue.jsの強みは リアクティブシステム にある(データが変われば画面が自動でついてくる)。
  • ref単一のリアクティブ変数(テンプレートでは .value 省略可)。
  • reactiveオブジェクト/配列をまるごとリアクティブ化(分割代入の罠に注意)。
  • computed:依存に応じて派生値を効率よく算出(再計算は必要時のみ)。
  • watch変化を監視して副作用を実行(APIや保存など“外の世界”と接続)。

まずは「ref で単一値」「reactive で塊」「表示の加工は computed」「外部処理は watch」という4本柱を覚えてしまう。
この「リアクティブの基礎」さえ掴めば、Vueの挙動は一気にクリアになります。

PR

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)