手写 vue 源码 === watch 实现
目录
1. watch 的基本使用
2. watch 的整体架构
3. doWatch 函数的实现
3.1 处理不同类型的监听源
3.2 清理副作用的机制
3.3 创建响应式效果
3.4 初始化执行
3.5 返回停止函数
4. watch 如何基于 ReactiveEffect 实现
4.1 依赖收集过程详解
4.2 更新触发过程详解
5. 深度遍历的实现细节
6. 实际执行流程示例
6.1 初始化阶段
6.2 更新阶段
7. watch 与 computed 的对比
8. 高级应用:清理副作用
总结
Vue 的响应式系统是其核心特性之一,而 watch
作为响应式系统的重要组成部分,允许我们监听数据变化并执行相应的回调函数。本文将深入探讨 Vue 中 watch
的实现原理,从源码角度逐步分析其工作机制。
1. watch 的基本使用
在深入实现原理之前,让我们先回顾一下 watch
的基本用法:
// 监听响应式对象
watch(state, (newVal, oldVal) => {console.log(newVal, oldVal);
});// 监听 ref 对象
watch(name, (newVal, oldVal) => {console.log(newVal, oldVal);
});// 监听 getter 函数
watch(() => state.name, (newVal, oldVal) => {console.log(newVal, oldVal);
}, {deep: true, // 是否深度监听immediate: true // 是否立即执行
});
2. watch 的整体架构
从源码中可以看出, watch
函数本质上是对 doWatch
函数的封装:
export function watch(source, cb, options = {} as any) {// watchEffect 也是基于这个实现return doWatch(source, cb, options);
}
而 watchEffect
也是基于同一个 doWatch
函数实现的,只是没有回调函数:
export function watchEffect(fn, options = {} as any) {return doWatch(fn, null, options);
}
3. doWatch 函数的实现
doWatch
函数是 watch
和 watchEffect
的核心实现,它处理不同类型的监听源,并设置相应的依赖收集和触发机制。
3.1 处理不同类型的监听源
doWatch
首先需要处理不同类型的监听源,将其转换为统一的 getter 函数:
function doWatch(source, cb, { deep, immediate }) {// source > getterconst ReactiveGetter = (source) =>traverse(source, deep === false ? 1 : undefined);let getter;let oldValue;let cleanup;// 处理不同类型的监听源if (isReactive(source)) {getter = () => ReactiveGetter(source);} else if (isRef(source)) {getter = () => source.value;} else if (isFunction(source)) {getter = source;}// ...
}
这里根据监听源的类型设置不同的 getter 函数:
- 对于响应式对象,使用 ReactiveGetter 函数遍历对象
- 对于 ref 对象,直接获取其 value 属性
- 对于函数,直接使用该函数作为 getter
3.2 清理副作用的机制
watch
提供了清理副作用的机制,通过 onCleanup
函数注册清理函数:
const onCleanup = (fn) => {cleanup = () => {fn();cleanup = null;};
};
这个机制允许我们在下一次回调执行前清理上一次回调产生的副作用,比如取消异步请求等。
3.3 创建响应式效果
doWatch
的核心是创建一个 ReactiveEffect
实例,它负责依赖收集和触发更新:
const job = () => {if (cb) {const newValue = effect.run();if (cleanup) {cleanup(); // 在执行回调之前,先执行清理}// 执行cbcb(newValue, oldValue, onCleanup);oldValue = newValu