受到疫情影響,絕大部分的時間不是在公司就是在家裡,每個禮拜出門採買一次。其實也不是說我不喜歡用線上購物,只是目前其實物流業也是相當緊繃。出門自己小心避開人潮,萬事小心謹慎點就好了。
至於避開人潮這件事情,我都平常日早上 8 點多去大潤發,大概可以避開八九成的人流。
I'm watching you
在 Vue3.x 之後呢,除了原有的 watch
之外,另外提供了一個叫做 watchEffect
的方法。前者跟 Vue2.x 的區別在於,在 3.x 以後深度監聽已經是預設,所以你不需要額外設定(但是)。而 watchEffect
在此則是全新的方法。
兩者的共通點:
- 響應式數值發生變化時觸發。
- 開發者可以執行指定函式(好像是廢話)。
- 提供一個方法讓開發者可終止函式(你可以想成
clearTimeout
之類的事情)。 - 終止函式隱含
onInvalidate
方法可以使用。
而兩者差異則有:
watch
本身屬於懶加載,所以第一次運行的時候不會動作。watch
提供新、舊數值內容(第三個參數則用來傳遞終止方法)。watchEffect
會立即執行,也就是第一次運行的時候就會觸發。watchEffect
自動監聽所有的響應式變數,你不用特別指定。
watch
由於在 3.x 的版本開始,預設就是深度監聽,所以,在使用上請特別小心。深度監聽到底做了什麼事情這邊就不贅述,請不要做那種我跳進來啦,我又跳出去啦的這種事情。
例如(以下範例地獄無窮迴圈,請不要隨意嘗試):
const counter = ref(0)
watch(counter, (newVal, oldVal) => {
console.log('我跳進來啦', newVal)
counter.value += 1
console.log('我又跳出去啦', counter.value)
})
又或者是,我們在搭配 Vue-Router 的時候,多半會去監聽網站的路徑是否有什麼變化,這個時候如果想不開,就會出現路由無限重複導向的狀況。範例就不列出來了,有經驗的人應該知道我再說什麼事情。
根據 Vue3.x 原始碼中所提到,他在執行 watch
的 queue 是使用 { flash: 'post', deep: true }
的預設參數去做設定,所以一開始才會說他是預設使用深度監聽。但是,
這個深度監聽是有些條件限制的。
首先,你所監聽的對象必須要擁有 getter/setter
的物件,也就是說,以下這樣的方式是可行的,
import { ref, watch } from 'vue'
export default {
name: 'ExampleComponent',
setup () {
const myVariable = ref(0)
watch(myVariable, (newVal) => {
console.log(newVal)
})
setTimeout(() => {
myVariable.value += 1
}, 1000)
return {
myVariable
}
}
}
當你把對象稍微調整一下,改由 reactive()
來做的時候,
import { reactive, watch } from 'vue'
export default {
name: 'ExampleComponent',
setup () {
const myVariable = reactive({
counter: 1
})
watch(myVariable, (newVal) => {
console.log(newVal)
})
setTimeout(() => {
myVariable.counter += 1
}, 1000)
return {
myVariable
}
}
}
然後你就會發現 watch
就不理你了。說好的預設是深度監聽呢?,其實 官方文件 有交代這些事情,可以的話回去喵一眼再回來看看這裡為何不會動。
所以基本上你還是必須做一個深度拷貝,或諸如此類的事情,這樣就會動作了,
import { reactive, watch } from 'vue'
export default {
name: 'ExampleComponent',
setup () {
const myVariable = reactive({
counter: 1
})
watch(() => ({...myVariable}), (newVal) => {
console.log(newVal)
}, {
deep: true
})
setTimeout(() => {
myVariable.counter += 1
}, 1000)
return {
myVariable
}
}
}
watchEffect
基本上跟 watch
很相似,你可以把他想成設定了 immediate
的 watch
的效果這樣。不過與 watch
稍微不同的地方在於,剛剛的例子,是會有反應的,
import { reactive, watch } from 'vue'
export default {
name: 'ExampleComponent',
setup () {
const myVariable = reactive({
counter: 1
})
watchEffect(() => {
console.log(newVal)
})
setTimeout(() => {
myVariable.counter += 1
}, 1000)
return {
myVariable
}
}
}
他也不需要加什麼 { deep: true }
這件事,感覺起來是不是比 watch
還要好用?具體上來說,這兩個方法應用的面向不太一樣,當然你想要全部都用某一種也是沒什麼關係。這裡沒有一定很好的作法,只是你需要考量監聽的對象,與是否需要持續監聽動作而已。
onInvalidate
無論是 watch
或是 watchEffect
都有這第三個參數可以使用,他的用意在於當監聽被取消時,這個方法就會被呼叫。
import { reactive, watch, watchEffect } from 'vue'
export default {
name: 'ExampleComponent',
setup () {
const myVariable = ref(0)
const stopWatchEffect = watchEffect((onInvalidate) => {
console.log(newVal)
onInvalidate(() => { ... })
})
const stopWatch = watch(myVariable, (newVal, oldVal, onInvalidate) => {
console.log(newVal)
onInvalidate(() => { ... })
})
setTimeout(() => {
myVariable.value += 1
}, 1000)
return {
myVariable
}
}
}
但有一個比較需要小心的地方,這一點需要貼一下 Vue3.x 原始碼給大家看一下,
let onInvalidate = (fn) => {
cleanup = runner.options.onStop = () => {
callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);
};
};
// 中略...
if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
// cleanup before running cb again
if (cleanup) {
cleanup();
}
callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onInvalidate
]);
oldValue = newValue;
}
如果你監聽的物件不是在你的元件內,例如說,
import { reactive, watch, watchEffect } from 'vue'
import useWindowResize from './useWindowResize.js'
export default {
name: 'ExampleComponent',
setup () {
const myVariable = useWindowResize()
const stopWatchEffect = watchEffect((onInvalidate) => {
console.log(myVariable)
onInvalidate(() => { ... })
})
const stopWatch = watch(() => ({...myVariable}), (newVal, oldVal, onInvalidate) => {
console.log(newVal)
onInvalidate(() => { ... })
}, {
deep: true
})
setTimeout(() => {
myVariable.value += 1
}, 1000)
return {
myVariable
}
}
}
以上有幾個雷點,
watchEffect
基本上看不到變化,這個時候watch
才會聽得到改變。onInvalidate()
會因為myVariable
的變化而一直被呼叫。stopWatch
被呼叫後,此時watch
才會停止監聽。
關於第 2 點,請務必留意。至於為什麼?我剛剛原始碼已經有貼上了,看不懂的話可以問一下官方為何要這樣設計。
小結
其實這次 Vue3.x 還有兩個 onTrack
, onTrigger
可用,主要目的是用來除錯用的,由於我超不會除錯所以這邊就不介紹這麼多了。
請記得 onInvalidate()
小心使用。