こんにちは、かつコーチです。
前回は <script setup> の基本と、ref ・ reactive の使い分けを解説しました。
今回は、データの変化を検知して処理を実行する「監視」の仕組み、watch と watchEffect を扱います。
どちらも「値の変化に反応する」という点は同じですが、使いどころが異なります。
この違いを理解しておかないと、「なぜか処理が実行されない」「逆に想定外のタイミングで実行される」といったハマりどころにつながるので、しっかり押さえていきましょう。
watchの基本
特定の値を監視する
watch は、監視したい値を明示的に指定するのが特徴です。
<script setup lang="ts">
import { ref, watch } from 'vue'
const keyword = ref<string>('')
watch(keyword, (newValue, oldValue) => {
console.log(`検索ワードが「${oldValue}」から「${newValue}」に変わりました`)
})
</script>
<template>
<input v-model="keyword" placeholder="検索ワードを入力" />
</template>
第1引数に「監視対象」、第2引数に「変化したときに実行する処理」を渡します。
コールバック関数では、変化後の値(newValue)と変化前の値(oldValue)の両方を受け取れるのが watch ならではの強みです。
複数の値をまとめて監視する
配列で複数の値を渡すと、まとめて監視できます。
<script setup lang="ts">
import { ref, watch } from 'vue'
const firstName = ref<string>('')
const lastName = ref<string>('')
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(`名前が「${newFirst} ${newLast}」に変わりました`)
})
</script>
どの値が変化のきっかけになったかまでは分かりませんが、「複数の値のどれかが変わったら実行したい」という場面では便利です。
watchEffectの基本
依存関係を自動で追跡する
watchEffect は、監視対象を明示的に指定しません。
関数の中で使われている ref や reactive の値を自動的に検知し、それらが変化するたびに実行してくれます。
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
const price = ref<number>(1000)
const quantity = ref<number>(2)
watchEffect(() => {
const total = price.value * quantity.value
console.log(`合計金額: ${total}円`)
})
</script>
このコードでは price と quantity の両方が自動的に監視対象になります。
さらに watchEffect は、コンポーネントが読み込まれた時点で即座に1回実行されるという特徴もあります。
watch は明示的に immediate: true を指定しない限り初回は実行されませんが、watchEffect はその手間が不要です。
つまずきやすいポイント:使い分けを間違える
「初期値だけ変化を無視したい」場面での失敗
私が実際に手を動かしていて混乱したのが、フォームの初期表示時に不要な処理が動いてしまったケースです。
❌ Before:watchEffectで不要な初回実行が起きてしまう
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
const searchKeyword = ref<string>('')
watchEffect(() => {
// 画面表示直後、searchKeywordが空文字の状態でも実行されてしまう
console.log(`APIを呼び出します: ${searchKeyword.value}`)
// fetchSearchResults(searchKeyword.value) のような処理を想定
})
</script>
このコードは、ユーザーが何も入力していない初期表示のタイミングでも1回実行されてしまいます。
「空文字で検索APIを叩いてしまう」という無駄な通信が発生し、私は「なぜ入力前にAPIが呼ばれているのか」としばらく原因を探すことになりました。
✅ After:watchを使い、値の変化のみに反応させる
<script setup lang="ts">
import { ref, watch } from 'vue'
const searchKeyword = ref<string>('')
watch(searchKeyword, (newValue) => {
if (newValue === '') return // 空文字なら何もしない
console.log(`APIを呼び出します: ${newValue}`)
// fetchSearchResults(newValue) のような処理を想定
})
</script>
watch に切り替え、監視対象を明示することで「初期表示時には実行せず、値が変化したときだけ動く」という意図がコードにはっきり表れます。
このように、「初回は実行したくない」「変化前後の値を比較したい」場面では watch、「複数の値の変化にまとめて反応させたい、初回も実行してよい」場面では watchEffect と覚えておくと使い分けに迷いません。
監視を止める:クリーンアップ
どちらの関数も、戻り値として「監視を停止する関数」を受け取れます。
<script setup lang="ts">
import { ref, watch } from 'vue'
const keyword = ref<string>('')
const stopWatch = watch(keyword, (newValue) => {
console.log(newValue)
})
// 特定の条件で監視を止めたいとき
// stopWatch()
</script>
無限にログを吐き続けるような処理を書いてしまったときは、この停止関数の存在を思い出すと解決の糸口になります。
応用:オプションを使いこなす
deepオプションとimmediateオプション
reactive で作ったオブジェクトの、ネストしたプロパティの変化まで検知したい場合は deep: true を指定します。
<script setup lang="ts">
import { reactive, watch } from 'vue'
const form = reactive({
user: {
name: '',
},
})
watch(
form,
() => {
console.log('フォームの内容が変化しました')
},
{ deep: true, immediate: true }
)
</script>
immediate: true を付ければ、watch でも初回に1回実行させることができます。
つまり watch に immediate: true を付けたものは、実質 watchEffect に近い挙動になります。
「明示的に監視対象を指定しつつ初回も実行したい」という細かい制御をしたい場合は、この組み合わせが役立ちます。
まとめ
この記事のポイント
watchは監視対象を明示的に指定し、変化前後の値を比較できるwatchEffectは依存する値を自動追跡し、初回も含めて実行される- 「初期表示時は処理を実行したくない」場面では
watchを使う - 監視を止めたいときは、戻り値の停止関数を呼び出す
deep・immediateオプションで細かい挙動を制御できる
次に読むべき記事
次回は、親コンポーネントから子コンポーネントへデータを渡す Props の定義と受け取り方を解説します。
→ 次の記事:Propsの定義と受け取り方