【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」。意図して“スナップショット(静的コピー)”が欲しいときだけ使う、が実務の定石
- 分割代入: リアクティブ性が切れてただの値になる(以後の変更は伝播しない)。
- 入れ子もOK:
user.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のリアクティブシステムは、以下のような流れで動作します。

PR
![]() | Vue 3 フロントエンド開発の教科書 [ WINGSプロジェクト 齊藤 新三 ] 価格:3960円 |

![]() | これからはじめるVue.js 3実践入門 [ 山田 祥寛 ] 価格:3740円 |

個人的によく湧き上がる疑問
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
