できない.dev

Vue で watch が発火しない

watch が反応しないのは、監視対象に値を直接渡しているか、ネストの変更を浅い監視で見逃しているのが主因。
ref はそのまま、reactive のプロパティはゲッター関数で渡し、深い変更は deep オプションを付ける。

#vue#watch#composition-api#reactivity#reactive

公開:

要約

watch のコールバックが呼ばれないときは、ほとんどが「監視対象の渡し方」の問題です。
Vue の watch は ref・ゲッター関数・reactive オブジェクトを監視対象に取れますが、reactive のプロパティを値のまま渡すと追跡できません。
ref はそのまま、reactive のプロパティはゲッター関数で渡すのが基本です。

よくある原因

  1. 値で渡している: watch(state.count, cb)state.count の評価結果(数値)を渡しているだけで、依存追跡が効かない。
  2. 浅い監視: reactive オブジェクトを監視しても、ref に入れたオブジェクトをゲッターで返すような場合はトップレベルの変更しか追わず、ネストの変更は検知されない。
  3. スナップショット: コンポーザブルなどから受け取った値をコピーして監視している。
  4. watchEffect の依存漏れ: コールバック内で実際にアクセスした値だけが依存になる。
    条件分岐で参照しなかった値は追跡されない。

解決策

1. ref はそのまま、reactive はゲッターで渡す

import { ref, reactive, watch } from 'vue'
 
const count = ref(0)
watch(count, (n) => console.log(n)) // ref はそのまま
 
const state = reactive({ count: 0 })
watch(() => state.count, (n) => console.log(n)) // ゲッターで渡す

ウォッチャの公式ガイド のとおり、watch(state.count, ...) のように値を直接渡すと監視は機能しません。

2. 深い変更は deep オプション

const obj = reactive({ nested: { value: 1 } })
 
watch(
  () => obj.nested,
  (n) => console.log(n),
  { deep: true },
)

reactive オブジェクトを直接渡した場合は自動的に deep になりますが、ゲッターでネストしたオブジェクトを返す場合は deep: true を明示します。

3. 初回実行は immediate

watch(
  source,
  (v) => console.log(v),
  { immediate: true }, // 監視開始時にも一度実行
)

4. watchEffect は参照した依存だけ追う

import { watchEffect } from 'vue'
 
watchEffect(() => {
  // ここでアクセスした reactive な値が自動的に依存になる
  console.log(state.count)
})

条件によって参照されない値は依存に含まれない点に注意します。
特定の値だけを監視したいなら watch を使う方が明示的です。

この記事は役立ちましたか?