Vue で watch が発火しない
watch が反応しないのは、監視対象に値を直接渡しているか、ネストの変更を浅い監視で見逃しているのが主因。
ref はそのまま、reactive のプロパティはゲッター関数で渡し、深い変更は deep オプションを付ける。
#vue#watch#composition-api#reactivity#reactive
公開:
要約
watch のコールバックが呼ばれないときは、ほとんどが「監視対象の渡し方」の問題です。
Vue の watch は ref・ゲッター関数・reactive オブジェクトを監視対象に取れますが、reactive のプロパティを値のまま渡すと追跡できません。
ref はそのまま、reactive のプロパティはゲッター関数で渡すのが基本です。
よくある原因
- 値で渡している:
watch(state.count, cb)はstate.countの評価結果(数値)を渡しているだけで、依存追跡が効かない。 - 浅い監視: reactive オブジェクトを監視しても、ref に入れたオブジェクトをゲッターで返すような場合はトップレベルの変更しか追わず、ネストの変更は検知されない。
- スナップショット: コンポーザブルなどから受け取った値をコピーして監視している。
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 を使う方が明示的です。