【Vue】watchとwatchEffectの使い分け

JavaScript

こんにちは、かつコーチです。

前回は <script setup> の基本と、refreactive の使い分けを解説しました。

今回は、データの変化を検知して処理を実行する「監視」の仕組み、watchwatchEffect を扱います。

どちらも「値の変化に反応する」という点は同じですが、使いどころが異なります。

この違いを理解しておかないと、「なぜか処理が実行されない」「逆に想定外のタイミングで実行される」といったハマりどころにつながるので、しっかり押さえていきましょう。

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 は、監視対象を明示的に指定しません。

関数の中で使われている refreactive の値を自動的に検知し、それらが変化するたびに実行してくれます。

<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>

このコードでは pricequantity の両方が自動的に監視対象になります。

さらに 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回実行させることができます。

つまり watchimmediate: true を付けたものは、実質 watchEffect に近い挙動になります。

「明示的に監視対象を指定しつつ初回も実行したい」という細かい制御をしたい場合は、この組み合わせが役立ちます。

まとめ

この記事のポイント

  • watch は監視対象を明示的に指定し、変化前後の値を比較できる
  • watchEffect は依存する値を自動追跡し、初回も含めて実行される
  • 「初期表示時は処理を実行したくない」場面では watch を使う
  • 監視を止めたいときは、戻り値の停止関数を呼び出す
  • deepimmediate オプションで細かい挙動を制御できる

次に読むべき記事

次回は、親コンポーネントから子コンポーネントへデータを渡す Props の定義と受け取り方を解説します。

→ 次の記事:Propsの定義と受け取り方

タイトルとURLをコピーしました