1. watch实现原理
所谓的watch,就是监听某数据的变化,从而触发一个指定的函数执行。举个例子:
watch(data, () => {
console.log('data 改变了')
})
data.a++
假设data是响应式数据,那么watch函数的作用就是监控data,当data改变时会调用传入的fn,打印出“data 改变了”。事实上watch函数的实现就是利用了effect函数以及options.schduler选项。
const watch = (obj, watchCb) => {
effect(() => obj.a, {
scheduler() {
watchCb()
}
})
}
const data = reactive({
a: 1,
b: 2,
c: 3
})
watch(data, () => {
console.log('data 改变了')
})
data.a++
在watch函数中,effect函数的第一个参数fn主要目的是读取obj.a,把fn这个依赖收集到obj.a的“桶”里。那么当obj.a改变的时候,就会通过trigger函数把“桶”里的fn.scheduler执行一次,我们在scheduler中执行一次watchCb,watch看似完成了,但如果改变data.b或data.c就不会生效了,原因很明显obj.b和obj.c的get方法没有触发,也就不存在依赖的收集。所以我们需要在effect函数的第一个参数fn中将传入obj的所有属性 进行遍历访问。
const watch = (obj, watchCb) => {
const traverse = (data) => {
if (typeof data === 'object') {
for (const key in data) {
traverse(data[key])
}
}
}
effect(() => traverse(obj), {
scheduler() {
watchCb()
}
})
}
2. 获取oldValue和newValue
目前的代码已经可以简单的实现watch,但我们在使用watch的时候可以在回调函数中拿到oldValue和newValue,就像这样:
watch(data, (nv, ov) => {
console.log('ov:',ov)
console.log('nv:',nv)
})
那么这个nv和ov应该如何得到呢,这又需要利用effect的lazy选项了。
const watch = (data, watchFn) => {
let oldVal
const traverse = (data) => {
if (typeof data === 'object') {
for (const key in data) {
traverse(data[key])
}
}
return JSON.parse(JSON.stringify(data))
}
const effectFn = effect(() => traverse(data), {
lazy: true,
scheduler() {
const newVal = effectFn()
watchFn(newVal, oldVal)
oldVal = newVal
}
})
oldVal = effectFn()
}
在这段代码中,我们声明变量effectFn并为它赋值为effect函数的返回值,因为加了lazy选项后effect传入的fn将不会自动执行,取而代之的则是oldVal = effectFn(),将oldValue的值先缓存下来。当data中有数据变动的时候,会触发scheduler函数,此时newVal = effectFn(),得到新的data值,随后将两个值传入watch回调中,最后再将newVal的值赋给oldValue。
3. Watch的immediate选项
有时候,我们希望当watch创建的时候就执行一次回调函数,在vue中的写法也比较简单,只需要多加一个immediate选项就能做到,像如下代码:
watch(data, (nv, ov) => {
console.log('ov:',ov)
console.log('nv:',nv)
}, {
immediate: true
})
这个功能简单来说,其实就是在effect执行结束后,oldValue = effectFn()之前执行一次scheduler方法咯,我们发现当scheduler执行结束后,oldValue会被正常赋值,所以oldValue = effectFn()似乎就不需要了。
const watch = (data, watchFn, options = {}) => {
let oldVal
const traverse = (data) => {
if (typeof data === 'object') {
for (const key in data) {
traverse(data[key])
}
}
return JSON.parse(JSON.stringify(data))
}
const scheduler = () => {
const newVal = effectFn()
watchFn(newVal, oldVal)
oldVal = newVal
}
const effectFn = effect(() => traverse(data), {
lazy: true,
scheduler
})
if (options.immediate) {
scheduler()
} else {
oldVal = effectFn()
}
}