一、
Q :因為 state 有內部的屬性 也就是id。 因為要追逐id。所以要寫函數?而不能直接監聽state。 只監聽state ,監聽不到id的變化嗎?
A:
為什么監聽 state 不等于監聽 state.id?
在 Vue 3 中,如果你這樣寫:
watch(state, () => {
console.log(‘state changed’);
})
這 只能監聽整個 state 對象的引用是否變化,而不是它內部某個屬性的變化。
🛑 舉個例子:
const state = reactive({
id: 1,
name: ‘Tom’
});
watch(state, () => {
console.log(‘state changed’);
});
現在你執行:
state.id = 2;
?這個時候,watch 是不會觸發的!
因為 state 本身(這個對象的引用)沒變,只是它里面的屬性變了。Vue 沒法“自動猜測”你想追蹤哪些屬性。
? 正確寫法:想監聽 id,就得顯式寫 () => state.id
watch(() => state.id, (newVal, oldVal) => {
console.log(‘id changed from’, oldVal, ‘to’, newVal);
});
這樣 Vue 就知道:“哦!你只關心 state.id 的變化。”
? 如果你真的想監聽整個對象的所有屬性怎么辦?
可以用 deep: true 選項:
watch(state, (newVal, oldVal) => {
console.log(‘state deep changed’);
}, { deep: true });
但注意這性能開銷比較大,而且也不夠精確,不推薦只為了監聽某個屬性而這樣寫。
? 總結一下
二、
Q:好像還有個shallowReactive什么的。可以用來做淺層和深層監聽
A:Vue 3 中確實有一套跟 “深層 / 淺層” 有關的工具,它們用來控制 響應式系統的行為,而不是直接控制 watch 的深度監聽。
我們來拆解清楚這幾個概念 👇
🧱 1. deep 是 watch 的選項
當你用 watch() 監聽一個對象時,Vue 默認只監聽它的“引用變化”,不會深層追蹤子屬性的變化。
? 用法:
watch(state, (newVal, oldVal) => {
console.log(‘deep change’);
}, { deep: true })
這個 { deep: true } 表示:Vue 會遞歸追蹤 state 內部所有的屬性變化。
🧪 舉個例子:
const state = reactive({
user: {
name: ‘Tom’,
age: 20
}
});
watch(state, () => {
console.log(‘state changed’);
}, { deep: true });
這時你做:
state.user.name = ‘Jerry’;
? 這會觸發監聽,因為你用了 deep: true。
🧊 2. shallowReactive 和 shallowRef
這是你剛剛提到的 shallow,它是另一個話題:
? 作用:只讓對象最外層變成響應式,里面嵌套的對象不會響應式化!
🔹shallowReactive 用法:
import { shallowReactive } from ‘vue’;
const state = shallowReactive({
user: {
name: ‘Tom’
}
});
這時,如果你改 state.user.name,Vue 不會追蹤到變化!
🔹對比 reactive:
const state = reactive({
user: {
name: ‘Tom’
}
});
這里 state.user.name 的變化會被 Vue 自動追蹤。
🧠 總結一下區別:
用法 深度追蹤 用途
reactive() ? 會遞歸處理內部對象 通常默認使用
shallowReactive() ? 只處理第一層,內部對象不是響應式 性能優化,避免不必要追蹤
watch(obj, fn, { deep: true }) ? 深層監聽 用于監聽嵌套對象屬性變化
watch(obj, fn) ? 默認只監聽對象引用變化 如果改屬性值,不觸發
🎯 你什么時候會用 shallowReactive?
? 當你知道你不需要監聽內部屬性,比如只監聽 API 結果是否更新,不關心結果結構。
? 或者你手動控制響應式,不希望 Vue 自動追蹤太多東西(性能優化場景)。
三、
Q:
x和y 都是ref(0)執行watch(x,()=>y.value],([newX,newY])=>{ })這什么意思? x和y的監聽有什么區別
A:
確實,從“效果”上來說,監聽 x 和監聽 () => y.value,在你這個例子中表現是一樣的。但是寫法不一樣,是因為 Vue 對 ref 和函數這兩種寫法的處理方式不同。
我們來徹底搞清楚:
? 結論先說:
雖然監聽效果一樣,但寫法上的不同是因為 Vue 對不同“源”的處理機制不一樣:
寫法 意義 Vue 的處理方式
x 是 ref 對象 Vue 自動監聽 x.value 的變化
() => y.value 是一個 getter 函數 Vue 追蹤這個函數里訪問的響應式依賴(即 y.value)
🧪 來看一組對比例子
const x = ref(1);
const y = ref(2);
watch([x, () => y.value], ([newX, newY]) => {
console.log(newX, newY);
});
你也可以這樣寫:
watch([() => x.value, () => y.value], ([newX, newY]) => {
console.log(newX, newY);
});
或者:
watch([x, y], ([newX, newY]) => {
console.log(newX, newY);
});
?? 這些寫法最終都能監聽 x.value 和 y.value 的變化。
?那為什么還要寫 () => y.value 呢?
這其實是 靈活性 + 一致性 的需要,以下場景中就必須使用 getter(函數):
? 1. 監聽多個響應式值組合而成的結果
watch(() => x.value + y.value, (sum) => {
console.log(‘sum changed to’, sum);
});
Vue 會追蹤 x.value 和 y.value,因為這兩個都在函數體中。
? 2. 監聽嵌套屬性(不是 ref)
const state = reactive({ a: { b: 1 } });
watch(() => state.a.b, (newVal) => {
console.log(‘b changed:’, newVal);
});
? 3. 統一寫法增強可讀性
有時你團隊約定都用 getter 函數,這樣避免有人忘了 .value 或混用 ref/reactive。
💡最佳實踐建議
情況 推薦寫法
監聽 ref 值(簡單場景) 直接寫 x
監聽多個響應式值組合 () => x.value + y.value
監聽深層屬性 / reactive 屬性 () => obj.nested.prop
想統一寫法、避免歧義 全部寫成 () => xxx.value
? 最后總結一句話:
寫成 x 是 Vue 對 ref 的“糖”,寫成 () => y.value 是通用的 getter。雖然它們監聽效果一樣,但表達方式和使用場景略有不同。
四、watch是有返回值的?
是的,watch 在 Vue 3 中 是有返回值的!
watch 返回一個 停止監聽的函數,也叫做 stop handle。
你可以在不需要監聽的時候,調用它來 手動停止這個 watch。
🧪 舉個例子:
import { ref, watch } from ‘vue’;
const count = ref(0);
const stop = watch(count, (newVal) => {
console.log(‘count changed to’, newVal);
});
// 5秒后停止監聽
setTimeout(() => {
stop(); // 👈 調用這個函數,watch 不再觸發
console.log(‘Stopped watching count’);
}, 5000);
? 常見用途:
1. 組件銷毀前手動清理監聽
(不過通常不需要,因為 Vue 會自動清理)
2. 只監聽一次某個變化后就停止
const stop = watch(count, (val) => {
if (val > 10) {
console.log(‘Too big!’);
stop(); // 👈 只監聽一次滿足條件后停止
}
});
🧠 總結:
特性 說明
返回值 是一個函數(stop handle)
作用 調用后立即停止當前的監聽器
適用場景 想主動控制監聽生命周期(如監聽一次或臨時監聽)
五、回顧一下箭頭函數