前言
这一节讲一下 watch和 watchEffect 函数的实现。
watch & watchEffect
export function watch(source, cb: Function, options = {} as any) {
return doWatch(source, cb, options);
}
export function watchEffect(getter: Function, options = {} as any) {
return doWatch(getter, undefined, options);
}
watch 和 watchEffect 函数的实现都调用了 doWatch 函数。
只不过 watch 函数在 dowatch 函数中传入了第二个 cb 参数,watchEffect 函数则给第二个参数传了 undefined。
接下来我们看一下,doWatch 函数是如何实现的:
function doWatch(source, cb: Function, { deep = false, immidiate }) {
const reactiveGetter = source => traverse(source, deep === false ? 1 : void 0);
let getter;
if (isReactive(source)) {
// 如果是响应性对象,循环遍历每个 key 都会触发 getter 函数, 进而出发依赖收集
getter = () => reactiveGetter(source);
} else if (isRef(source)) {
// 如果是 ref 对象,获取 value 值, 也会触发依赖收集
getter = () => source.value;
} else if (isFunction(source)) {
// 如果是函数,直接赋值给 getter
getter = source;
}
let oldValue;
const job = () => {
const newValue = effect.run();
cb && cb(newValue, oldValue);
oldValue = newValue;
};
const effect = new ReactiveEffect(getter, job);
oldValue = effect.run();
// 立即执行一次回调
if (immidiate) {
cb && cb(oldValue, undefined);
}
const unwatch = () => {
effect.stop();
};
return unwatch;
}
dowatch 接收三个参数,第一个参数是响应性变量或者是一个 effect 函数,第二个参数是一个回调函数,第三个参数是一个选项对象。
dowatch 函数首先创建了一个 reactiveGetter 函数,这个函数通过调用 traverse 函数来创建响应性 getter 函数。
// 遍历对象的每个 key 就会触发 这个对象的 getter 函数
function traverse(source, depth, currentDepth = 0, seen = new Set()) {
if (!isObject(source)) {
return source;
}
// 控制遍历深度
if (depth) {
if (currentDepth >= depth) {
return source;
}
currentDepth++;
}
// 防止循环遍历
if (seen.has(source)) {
return source;
}
seen.add(source);
for (const key in source) {
traverse(source[key], depth, currentDepth, seen);
}
return source;
}
traverse 函数的作用就是遍历对象,如果 deep 为 true,则会递归遍历所有子属性。
遍历对象的作用就是为了触发这个对象上的属性的 getter 方法,进而触发依赖收集。
然后,创建一个 getter 变量,通过判断传进来的 source 来给 getter 赋值:
- 如果
source是响应性对象,则将reactiveGetter赋值给getter; - 如果
source是ref对象,则创建一个函数,去读取source.value,再将函数赋值给getter; - 如果
source是函数,则直接赋值给getter。
其实看到这,我们就能明白了,为什么在监听一个 ref 对象时,可以直接监听这个对象,也可以传入一个函数,但函数内部必须要读取这个 ref 对象的 value 值。因为要触发依赖收集。
然后,创建一个 oldValue 变量,用来存放旧值。
同时创建一个 job 函数,用来作为 effect 的 scheduler 函数。
const job = () => {
const newValue = effect.run();
cb && cb(newValue, oldValue);
oldValue = newValue;
};
job 函数,首先会执行一遍 effect 的 run 方法,获取新值,然后调用回调函数,并传入新值和旧值。然后,将新值赋值给 oldValue。
然后创建一个 effect 对象,并传入 getter 函数和 job 函数。并执行一遍 effect 的 run 方法,获取旧值。
接下来就是 判断 有没有传入 immediate 选项,如果有,则立即执行一次回调函数。
最后,创建一个 unwatch 函数,用来停止依赖收集,并返回这个 unwatch 函数。
至此,watch 和 watchEffect 函数的实现就讲完了。