專欄分享:vue2源碼專欄,vue3源碼專欄,vue router源碼專欄,玩具項目專欄,硬核??推薦??
歡迎各位ITer關注點贊收藏??????
語法
偵聽一個或多個響應式數(shù)據(jù)源,并在數(shù)據(jù)源變化時調(diào)用所給的回調(diào)函數(shù)
const x = ref(0)
const y = ref(0)
// 單個 ref
watch(x, (newValue, oldValue) => {
console.log(`x is ${newValue}`)
})
// getter 函數(shù)
watch(
() => x.value + y.value,
(newValue, oldValue) => {
console.log(`sum of x + y is: ${newValue}`)
}
)
// 多個來源組成的數(shù)組
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
第一個參數(shù)可以是不同形式的“數(shù)據(jù)源”:它可以是一個 ref (包括計算屬性)、一個響應式對象、一個 getter 函數(shù)、或多個數(shù)據(jù)源組成的數(shù)組
第二個參數(shù)是在發(fā)生變化時要調(diào)用的回調(diào)函數(shù)。這個回調(diào)函數(shù)接受三個參數(shù):新值、舊值,以及一個用于注冊副作用清理的回調(diào)函數(shù)。該回調(diào)函數(shù)會在副作用下一次重新執(zhí)行前調(diào)用,可以用來清除無效的副作用,例如等待中的異步請求。
第三個可選的參數(shù)是一個對象,支持以下這些選項:
- immediate:在偵聽器創(chuàng)建時立即觸發(fā)回調(diào)。第一次調(diào)用時舊值是 undefined。
- deep:如果源是對象,強制深度遍歷,以便在深層級變更時觸發(fā)回調(diào)。參考深層偵聽器。
- flush:調(diào)整回調(diào)函數(shù)的刷新時機。參考回調(diào)的刷新時機及 watchEffect()。
- onTrack / onTrigger:調(diào)試偵聽器的依賴。參考調(diào)試偵聽器。
源碼實現(xiàn)
-
@issue1 深度遞歸循環(huán)時考慮對象中有循環(huán)引用的問題
-
@issue2 兼容數(shù)據(jù)源為響應式對象和getter函數(shù)的情況
-
@issue3 immediate回調(diào)執(zhí)行時機
-
@issue4 onCleanup該回調(diào)函數(shù)會在副作用下一次重新執(zhí)行前調(diào)用
/**
* @desc 遞歸循環(huán)讀取數(shù)據(jù)
* @issue1 考慮對象中有循環(huán)引用的問題
*/
function traversal(value, set = new Set()) {
// 第一步遞歸要有終結(jié)條件,不是對象就不在遞歸了
if (!isObject(value)) return value
// @issue1 處理循環(huán)引用
if (set.has(value)) {
return value
}
set.add(value)
for (let key in value) {
traversal(value[key], set)
}
return value
}
/**
* @desc watch
* @issue2 兼容數(shù)據(jù)源為響應式對象和getter函數(shù)的情況
* @issue3 immediate 立即執(zhí)行
* @issue4 onCleanup:用于注冊副作用清理的回調(diào)函數(shù)。該回調(diào)函數(shù)會在副作用下一次重新執(zhí)行前調(diào)用,可以用來清除無效的副作用,例如等待中的異步請求
*/
// source 是用戶傳入的對象, cb 就是對應的回調(diào)
export function watch(source, cb, { immediate } = {} as any) {
let getter
// @issue2
// 是響應式數(shù)據(jù)
if (isReactive(source)) {
// 遞歸循環(huán),只要循環(huán)就會訪問對象上的每一個屬性,訪問屬性的時候會收集effect
getter = () => traversal(source)
} else if (isRef(source)) {
getter = () => source.value
} else if (isFunction(source)) {
getter = source
}else {
return
}
// 保存用戶的函數(shù)
let cleanup
const onCleanup = fn => {
cleanup = fn
}
let oldValue
const scheduler = () => {
// @issue4 下一次watch開始觸發(fā)上一次watch的清理
if (cleanup) cleanup()
const newValue = effect.run()
cb(newValue, oldValue, onCleanup)
oldValue = newValue
}
// 在effect中訪問屬性就會依賴收集
const effect = new ReactiveEffect(getter, scheduler) // 監(jiān)控自己構(gòu)造的函數(shù),變化后重新執(zhí)行scheduler
// @issue3
if (immediate) {
// 需要立即執(zhí)行,則立刻執(zhí)行任務
scheduler()
}
// 運行getter,讓getter中的每一個響應式變量都收集這個effect
oldValue = effect.run()
}
測試代碼
循環(huán)引用
對象中存在循環(huán)引用的情況文章來源:http://www.zghlxwxcb.cn/news/detail-760482.html
const person = reactive({
name: '柏成',
age: 25,
address: {
province: '山東省',
city: '濟南市',
}
})
person.self = person
watch(
person,
(newValue, oldValue) => {
console.log('person', newValue, oldValue)
}, {
immediate: true
},
)
數(shù)據(jù)源
- 數(shù)據(jù)源為 ref 的情況,和 immediate 回調(diào)執(zhí)行時機
const x = ref(1)
watch(
x,
(newValue, oldValue) => {
console.log('x', newValue, oldValue)
}, {
immediate: true
},
)
setTimeout(() => {
x.value = 2
}, 100)
- 兼容數(shù)據(jù)源為 響應式對象 和 getter函數(shù) 的情況,和 immediate 回調(diào)執(zhí)行時機
const person = reactive({
name: '柏成',
age: 25,
address: {
province: '山東省',
city: '濟南市',
}
})
// person.address 對象本身及其內(nèi)部每一個屬性 都收集了effect。traversal遞歸遍歷
watch(
person.address,
(newValue, oldValue) => {
console.log('person.address', newValue, oldValue)
}, {
immediate: true
},
)
// 注意!我們在 watch 源碼內(nèi)部滿足了 isFunction 條件
// 此時只有 address 對象本身收集了effect,僅當 address 對象整體被替換時,才會觸發(fā)回調(diào);
// 其內(nèi)部屬性發(fā)生變化并不會觸發(fā)回調(diào)
watch(
() => person.address,
(newValue, oldValue) => {
console.log('person.address', newValue, oldValue)
}, {
immediate: true
},
)
// person.address.city 收集了 effect
watch(
() => person.address.city,
(newValue, oldValue) => {
console.log('person.address.city', newValue, oldValue)
}, {
immediate: true
},
)
setTimeout(() => {
person.address.city = '青島市'
}, 100)
onCleanup
watch回調(diào)函數(shù)接受三個參數(shù):新值、舊值,以及一個用于注冊副作用清理的回調(diào)函數(shù)(即我們的onCleanup)。該回調(diào)函數(shù)會在副作用下一次重新執(zhí)行前調(diào)用,可以用來清除無效的副作用,例如等待中的異步請求。文章來源地址http://www.zghlxwxcb.cn/news/detail-760482.html
const person = reactive({
name: '柏成',
age: 25
})
let timer = 3000
function getData(timer) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(timer)
}, timer)
})
}
// 1. 第一次調(diào)用watch的時候注入一個取消的回調(diào)
// 2. 第二次調(diào)用watch的時候會執(zhí)行上一次注入的回調(diào)
// 3. 第三次調(diào)用watch會執(zhí)行第二次注入的回調(diào)
// 后面的watch觸發(fā)會將上次watch中的 clear 置為true
watch(
() => person.age,
async (newValue, oldValue, onCleanup) => {
let clear = false
onCleanup(() => {
clear = true
})
timer -= 1000
let res = await getData(timer) // 第一次執(zhí)行2s后渲染2000, 第二次執(zhí)行1s后渲染1000, 最終應該是1000
if (!clear) {
document.body.innerHTML = res
}
},
)
person.age = 26
setTimeout(() => {
person.age = 27
}, 0)
到了這里,關于【源碼系列#04】Vue3偵聽器原理(Watch)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!