Vue.js: ウォッチャーwatch

最終更新:2026-10-20

リスナー(watch / watchEffect)を使用すると、リアクティブデータの変更を監視し、その変更が発生した際に副作用を実行することができます。computedは「新しい値を導出」するために使用され、watchは「アクションをトリガー」するために使用されます(リクエストの発行や状態の設定など)。

watch は Vue のリアクティブシステムの重要な構成要素であり、主に データの変更時に API 呼び出しをトリガーすること、フォーム入力のデバウンス、およびデータ間の同期 という 3 つのユースケースがあります。

関連基本:watchEffect() watch()

1. 学習内容


2. 検索ボックスに関するAPIのパフォーマンスの問題

(2) 課題:キーを押すたびにリクエストが送信され、APIがクラッシュしてしまう

アリスは管理画面に商品検索ボックスを実装しました:

JS
// ❌ The "Broken" Version:A request is sent for each character
import { ref } from 'vue'

const searchQuery = ref('')

watch(searchQuery, async (newValue) => {
  // A request is sent with every input
  const results = await fetch(`/api/search?q=${newValue}`)
  searchResults.value = await results.json()
})

APIサーバーがクラッシュしました:

バックエンド開発者のチャーリーは次のように報告しています:

「アリス、あなたの検索処理がAPIに過度な負荷をかけています。デバウンスが必要です。ユーザーが入力を止めてから300ミリ秒待ってからリクエストを送信してください。」

(2) ウォッチビュー+手ぶれ補正ソリューション

JS
import { ref, watch } from 'vue'

const searchQuery = ref('')
const searchResults = ref([])

// Image Stabilization:300ms A request is only sent if there are no internal changes.
let timeoutId = null
watch(searchQuery, (newValue) => {
  if (timeoutId) clearTimeout(timeoutId)
  timeoutId = setTimeout(async () => {
    const response = await fetch(`/api/search?q=${newValue}`)
    searchResults.value = await response.json()
  }, 300)
})

(3) 収益

デバウンス処理を実装した後:

▶ サンプル:デバウンスとスロットリングの実装

JS
import { ref, watch } from 'vue'

const searchQuery = ref('')

// Image Stabilization:300ms Last trigger inside
let debounceId = null
watch(searchQuery, (val) => {
  if (debounceId) clearTimeout(debounceId)
  debounceId = setTimeout(() => {
    console.log('Search:', val)
  }, 300)
})

// Cost-Cutting: Max 1 trigger per second
const scrollY = ref(0)
let lastRun = 0
watch(scrollY, (val) => {
  const now = Date.now()
  if (now - lastRun >= 1000) {
    lastRun = now
    console.log('Scroll:', val)
  }
})
▶ 試してみよう

出力:

TEXT
iphone と入力している間は出力なし、停止して 300ms 後に Search: iphone
スクロールは 1 秒に 1 回だけ Scroll: <scrollY> を出力する

(1) 📊 . よくある5つの間違いに関するクイックリファレンス

エラー 症状 解決策
「deep」を設定せずにリアクティブを監視する 内部プロパティの変更はトリガーされない watch(obj, fn, { deep: true })
refラッパーオブジェクト全体を監視する 内部プロパティの変更ではトリガーされない リアクティブに切り替えるか、「deep」を追加する
watchEffect が無限ループを引き起こす ブラウザがフリーズする 関数内で使用されるデータの変更は避ける
タイマーのクリーンアップが行われていない メモリリーク onCleanup を使用する
直ちに難読化を実行 以前の値を使いたいが取得できなかった immediate + oldVal を使用(未定義の場合あり)


3. watch()の基本構文

(1) 4つのパラメータ

JS
import { ref, watch } from 'vue'

const count = ref(0)

watch(
  // 1. Monitoring Source:Reactive data to be monitored
  count,
  // 2. Callback function:(New Value, Old value) => {...}
  (newVal, oldVal) => {
    console.log(`count: ${oldVal} → ${newVal}`)
  },
  // 3. Options: immediate, deep, flush
  { immediate: true, deep: true }
)

(2) 審判の指示に耳を傾ける

JS
const count = ref(0)

// Monitoring ref
watch(count, (newVal, oldVal) => {
  console.log(`count: ${oldVal} → ${newVal}`)
})

(3) 複数の参照の監視

JS
const firstName = ref('Alice')
const lastName = ref('Smith')

// Monitor multiple
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log(`Name: ${newFirst} ${newLast}`)
})

▶ サンプル:レフェリー用のベーシックな腕時計

▶ 試してみよう

出力:

TEXT
Count: 0
入力欄に 1 と打つと Count: 1、続けて 2 で Count: 12
コンソール:Count changed: 0 → 1、Count changed: 1 → 12

4. 4種類のリスニングソース

(1) 審判の指示に耳を傾ける

JS
const count = ref(0)
watch(count, (newVal, oldVal) => {
  console.log(`count: ${oldVal} → ${newVal}`)
})

(2) リアクティブオブジェクトのリスニング

JS
import { reactive, watch } from 'vue'

const user = reactive({ name: 'Alice', age: 25 })

// Monitor the entire reactive(Automatic Depth)
watch(user, (newVal, oldVal) => {
  console.log('User changed:', newVal)
})

// ✅ Modifying internal properties also triggers
user.name = 'Bob'  // Trigger

(3) ゲッター関数のリスニング

JS
const state = reactive({ user: { profile: { name: 'Alice' } } })

// Monitor only specific properties(Precise Control)
watch(
  () => state.user.profile.name,
  (newVal) => {
    console.log(`Name: ${newVal}`)
  }
)

(4) computed に耳を澄ます

JS
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

watch(doubleCount, (newVal) => {
  console.log(`Double: ${newVal}`)
})

(4) 4種類のモニタリング情報源の比較

データソース 表記 詳細 適用対象
ref watch(ref, fn) デフォルトのライト 単一の参照
リアクティブ watch(reactive, fn) 自動ディープ オブジェクト
ゲッター watch(() => obj.x, fn) デフォルトの浅い参照 特定のプロパティ
計算値 watch(computed, fn) デフォルトの光 導出値

▶ サンプル:反応の深さの変化に注意を払う

JS
import { reactive, watch } from 'vue'

const user = reactive({
  profile: { name: 'Alice', age: 25 },
  settings: { theme: 'dark' }
})

// Monitor the entire user(Automatic Depth)
watch(user, (newVal) => {
  console.log('User changed:', JSON.stringify(newVal))
})

// ✅ Modifying any property triggers
user.profile.name = 'Bob'  // Trigger
user.settings.theme = 'light'  // Trigger
▶ 試してみよう

出力:

TEXT
watch 直後は何も出力されない(immediate なし)
user.profile.name = 'Bob' → User changed: {"profile":{"name":"Bob","age":25},"settings":{"theme":"dark"}}
settings.theme の変更でも同じログがもう一度出力される

5. watchEffect(): 自動依存関係追跡

(1) 基本的な構文

JS
import { ref, watchEffect } from 'vue'

const count = ref(0)
const name = ref('Alice')

// You don't need to specify who to listen for.,Reactive data used within the function body is automatically tracked
watchEffect(() => {
  console.log(`count: ${count.value}, name: ${name.value}`)
  // count Re-execute when changes occur
  // name Re-execute when changes occur
})

(2) watchEffect 対 watch

アスペクト ウォッチ ウォッチエフェクト
再生ソース 手動指定 自動追跡
今すぐ実行 デフォルト:いいえ (immediate: true が必要) ✅ はい
従来の価値観と新しい価値観 ✅ 両方 ❌ 新しい価値観のみ
クリーンアップの副作用 onCleanup onCleanup
ユースケース 精密なモニタリング 一般的な副作用

(3) watchEffect のクリーンアップ

JS
import { watchEffect, onCleanup } from 'vue'

watchEffect((onCleanup) => {
  // Set a Timer, Subscribe, Event Listening
  const timer = setInterval(() => {
    console.log('tick')
  }, 1000)
  
  // Cleanup:Called before the next execution or when the component is unloaded
  onCleanup(() => {
    clearInterval(timer)
  })
})

▶ サンプル:watchEffect による自動追跡

JS
import { ref, watchEffect } from 'vue'

const count = ref(0)
const name = ref('Alice')

// Do not specify a source to listen to,Automatic tracking used in the function body
watchEffect(() => {
  console.log(`count: ${count.value}, name: ${name.value}`)
  // count Re-execute when changes occur
  // name Re-execute when changes occur
})

// Execute Now 1 time
count.value = 1  // Re-execute
name.value = 'Bob'  // Re-execute
▶ 試してみよう

出力:

TEXT
実行直後:count: 0, name: Alice
count.value = 1 → count: 1, name: Alice
name.value = 'Bob' → count: 1, name: Bob

6. オプション:immediate / deep / flush

(1) 即時:直ちに発動する

JS
const count = ref(0)

// Default:count Change is what triggers it
watch(count, (newVal) => console.log(newVal))  // Will not be executed immediately

// immediate: true: Trigger Immediately 1 time
watch(count, (newVal) => console.log(newVal), { immediate: true })
// ✅ Execute Now:0 → 0

(2) deep: 詳細な監視

JS
import { ref, watch } from 'vue'

const user = ref({ profile: { name: 'Alice' } })

// Default:ref The object being wrapped .value It is triggered only when the entire replacement is made
watch(user, () => console.log('changed'))  // ❌ user.value.profile.name = 'Bob' Does not trigger

// deep: true:Changes to internal properties also trigger
watch(user, () => console.log('changed'), { deep: true })
// ✅ user.value.profile.name = 'Bob' Trigger

(3) flush:トリガーされたとき

JS
// Default 'pre':Triggered before the component is updated
watch(source, fn, { flush: 'pre' })

// 'post':Triggered after a component update(DOM Updated)
watch(source, fn, { flush: 'post' })

// 'sync':Synchronous Triggering(Not commonly used,Poor performance)
watch(source, fn, { flush: 'sync' })

7. 総合演習:検索ボックスとデバウンス

▶ サンプル:検索のデバウンス

▶ 試してみよう

出力:

TEXT
初期表示:Results: 0 items(入力欄は空)
Shoes と入力して 300ms 経つと /api/search?q=Shoes の件数が Results: N items に反映
入力を消すと Results: 0 items に戻る


関連基本:watchEffect() watch()

❓ よくある質問

Q watch と watchEffect、どちらを使うべきですか?
A 可能な限り watch を使用してください(正確な制御や、新旧の値へのアクセスが可能になります)。watchEffectは、特定のデータが変更されたときにアクションを実行したいが、すべての依存関係を一覧表示したくない場合に適しています。パフォーマンス面では、これらはおおむね同等ですが、watchEffectは(関数本体内で依存関係を追跡する必要があるため)わずかに遅くなります。
Q ref を監視する場合、旧値と新値の型は何ですか?
A .value です。ref がオブジェクトをラップしている場合、旧値と新値の両方が ref.value になります(値全体が置き換えられない限り、参照は同じです)。内部プロパティの変更を監視するには、reactive またはゲッター関数を使用してください。
Q watch を使用して ref ラッパーオブジェクトをオブザーブする場合、その内部プロパティをどのようにオブザーブすればよいですか?
A 3つの方法があります:(1) watch(ref, fn, { deep: true });(2) watch(() => ref.value.x, fn)(ゲッター);(3) リアクティブラッパーに切り替える。
Q watch はプロパティを監視できますか?
A はい。watch(() => props.count, fn)。なお、プロパティは読み取り専用ですので、watch コールバック内で変更しないでください。
Q immediate: true のとき、oldVal とは何ですか?
A undefined です(最初のトリガー時には「以前の値」は存在しません)。以前の値は、リファレンスの初期値またはストアから取得できます。
Q watch は非同期に実行できますか?
A コールバック関数は async として実装できますが、watch 自体は同期的にトリガーされます。非同期ロジックはコールバック関数内に記述してください:(newVal) => { setTimeout(() => {...}, 0) }。
Q onCleanup はいつトリガーされますか?
A 以下の 2 つのタイミングでトリガーされます:(1) ウォッチが再実行される前(最後のクリーンアップの後)、(2) コンポーネントがアンマウントされたとき。タイマーのクリーンアップ、リクエストのキャンセル、イベントリスナーの削除などに適しています。

📖 まとめ


📝 練習問題

  1. 基礎問題(難易度:⭐)

    watch を使用して、ref の変数 count を監視します:

    • カウントが変更されたら、「count: ${old} → ${new}」と表示する
    • immediate: true を使用すると、コールバックを一度だけ即座に実行します
  2. 上級問題(難易度:⭐⭐)

    watch を使用して「自動保存」機能を実装する:

    • 参照テキストを編集 content
    • 1秒後に自動的にsave(content)関数を呼び出します(デバウンス)
    • 別のページに切り替える際(コンポーネントのアンロード時)にタイマーをリセットする
  3. チャレンジ問題(難易度:⭐⭐⭐)

    検索、オートコンプリート、履歴のシステムを完全に実装する:

    1. 手ぶれ補正検索(300 ms)
    2. 検索結果の一覧を表示する(v-for)
    3. 直近5件の検索履歴を保存する
    4. 履歴項目をクリックして、再度検索します
    5. 検索語を変更する際、保留中のリクエストをキャンセルする
    6. watchEffect および onCleanup を使用して実装する
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%