watch 函數分為兩部分:監視的數據源,要執行的動作。
先看一個列子,列出了幾種常用數據源的情況:
- ref變量,
- 多個 ref 變量形成一個數據源,
- reactive 變量,默認使用深度監聽,不需要像早期版本那樣額外標明deep
- 函數。
點擊查看代碼
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const message = ref('Hello')
// 基礎用法:監聽單個數據源
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 監聽多個數據源
watch([count, message], ([newCount, newMsg], [oldCount, oldMsg]) => {
console.log(`count: ${oldCount} -> ${newCount}`)
console.log(`message: ${oldMsg} -> ${newMsg}`)
})
// 監聽響應式對象
const user = reactive({
name: 'John',
age: 20
})
//不需要 deep 選項:深度監聽對象的所有屬性變化
watch(user, (newValue, oldValue) => {
console.log('user changed:', newValue)
}/*, { deep: true }*/)
</script>
我們看到vue源代碼對數據源是怎麼處理的:
- 首先數據源必須是 getter 函數,如果不是將其轉化為 getter 函數
** 將 ref 變量轉化成 getter 函數。
** 將 reactive 變量轉化成 getter 函數。
** 將 數組 轉化成 getter 函數
** 將 函數 轉化成 getter 函數
點擊查看代碼
let getter: () => any
let forceTrigger = false
let isMultiSource = false
// 1. 處理 ref 類型
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
}
// 2. 處理 reactive 對象
else if (isReactive(source)) {
getter = () => reactiveGetter(source)
forceTrigger = true
}
// 3. 處理數組(多數據源)
else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return reactiveGetter(s)
} else if (isFunction(s)) {
return call ? call(s, WatchErrorCodes.WATCH_GETTER) : s()
} else {
__DEV__ && warnInvalidSource(s)
}
})
}
// 4. 處理函數
else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = call
? () => call(source, WatchErrorCodes.WATCH_GETTER)
: (source as () => any)
} else {
// watchEffect
getter = () => {
if (cleanup) {
pauseTracking()
try {
cleanup()
} finally {
resetTracking()
}
}
const currentEffect = activeWatcher
activeWatcher = effect
try {
return call
? call(source, WatchErrorCodes.WATCH_CALLBACK, [boundCleanup])
: source(boundCleanup)
} finally {
activeWatcher = currentEffect
}
}
}
}
// 5. 處理無效數據源
else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
所以:
- ref 轉化成 ()=>xxx.value.
** 注意這裏很明顯只監視xxx.value的變化,所以只追蹤了一層,所以:
*** 如果 ref 變量本身包含的是簡單值,比如number,string,bool,那麼沒問題。
*** 如果 ref 變量本身包含的是對象值,比如object,array 等等,那麼不會深層監視,只有對象整個變化了才能觸發watch動作。 - reactive 轉化成 () => reactiveGetter(xxx)
** 看下面reactiveGetter()函數,可以看到默認路徑是深度跟蹤的。 - 函數形式,直接用函數作為 getter 函數
點擊查看代碼
const reactiveGetter = (source: object) => {
//如果用option來指明深度跟蹤,我們統一到後面處理,這裏就返回簡單的一層。
// traverse will happen in wrapped getter below
if (deep) return source
//如果用option來指明不深度 deep: false | 0, 那麼只追蹤一層。
// for `deep: false | 0` or shallow reactive, only traverse root-level properties
if (isShallow(source) || deep === false || deep === 0)
return traverse(source, 1)
//默認路徑是深度跟蹤的。
// for `deep: undefined` on a reactive object, deeply traverse all properties
return traverse(source)
}
所以,
- 簡單基本數據,建議使用 ref 作為數據源,只監視一層。
- 簡單對象數據,建議使用 reactive 作為數據源,深度監視。比如只有1層的對象。
- 複雜對象數據,建議使用函數明確的挑出追蹤數據點。
- 頭腦比較混亂時,一律使用函數明確要監視的數據點。
最後,
因為 vue 推薦使用 ref 包裝對象,所以我們很多時候會用 ref 包裝對象。那麼要監視這種ref對象的內部變動就有點特殊,因為
最為 ref ,傳遞給 watch 是隻能監視一層。沒法深層監視。那麼:
- 要麼使用 函數 來標明要監視的數據點,
- 要麼額外加 deep 屬性,實現全部深度監視。
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。