Vue 3.0中响应式依赖和更新
响应式依赖和更新是Vue 3.0中最重要的机制,其核心代码如下,本文将结合代码对这个设计机制作出一些解释。
// 全局依赖存储:WeakMap<target, Map<key, Set<effect>>>
const targetMap = new WeakMap();// 当前活动的副作用函数(如组件的渲染函数)
let activeEffect = null;// 响应式入口函数
function reactive(target) {return createReactiveObject(target, mutableHandlers);
}// 创建响应式代理对象
function createReactiveObject(target, handlers) {if (typeof target !== 'object' || target === null) {return target; // 非对象直接返回}return new Proxy(target, handlers);
}// Proxy 拦截器配置
const mutableHandlers = {get(target, key, receiver) {track(target, key); // 依赖收集const res = Reflect.get(target, key, receiver);if (typeof res === 'object' && res !== null) {return reactive(res); // 深度响应式}return res;},set(target, key, value, receiver) {const oldValue = target[key];const result = Reflect.set(target, key, value, receiver);if (oldValue !== value) {trigger(target, key); // 触发更新}return result;},// 其他拦截器(deleteProperty/has/ownKeys 等省略)
};// 依赖收集:建立 target.key → effect 的映射
function track(target, key) {if (!activeEffect) return; // 无活动 effect 则退出let depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}let dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = new Set()));}if (!dep.has(activeEffect)) {dep.add(activeEffect); // 添加 effect 到依赖集合activeEffect.deps.push(dep); // 反向关联(用于清理)}
}// 触发更新:执行 target.key 的所有依赖 effect
function trigger(target, key) {const depsMap = targetMap.get(target);if (!depsMap) return;const effects = new Set();const dep = depsMap.get(key);if (dep) {dep.forEach(effect => effects.add(effect));}effects.forEach(effect => effect()); // 重新执行副作用函数
}// 副作用函数封装(示例)
function effect(fn) {const effectFn = () => {activeEffect = effectFn;fn();activeEffect = null;};effectFn.deps = []; // 存储所有关联的 dep 集合effectFn();
}
1. 核心数据结构:targetMap
类型:WeakMap<Object, Map<string, Set<Effect>>>
作用:全局存储所有响应式对象的依赖关系。
键:被代理的原始对象Object。
值:一个 Map,其键是对象的属性名,值是一个 Set,存储了与该属性相关的所有副作用函数,如组件的渲染函数。
2. track 函数:依赖收集
触发时机:当访问响应式对象的某个属性时,即触发get操作。
核心流程:
1. 获取当前活动的副作用函数:activeEffect 指向当前正在执行的副作用函数,例如组件渲染函数或 watch 回调;
2. 初始化依赖关系:从 targetMap 中获取目标对象 target 对应的 depsMap,若不存在,则新建一个 Map。从 depsMap 中获取属性 key 对应的依赖集合 dep,若不存在,则新建一个 Set;
3. 建立双向关联:将 activeEffect 添加到 dep 中,表示该属性依赖此副作用函数。将 dep 添加到 activeEffect.deps中,用于后续清理无效依赖,避免内存泄漏;
const obj = reactive({ count: 0 });
effect(() => console.log(obj.count));
// 执行 effect 时,`activeEffect` 指向该函数
// 访问 obj.count 时触发 track,将 effect 函数添加到 count 的依赖集合中。
3. trigger 函数:触发更新
触发时机:当修改响应式对象的属性时 ,即触发set 操作。
核心流程:
1. 获取目标对象的依赖集合:从 targetMap 中获取 target 对应的 depsMap;
2. 收集所有相关副作用函数:从 depsMap 中获取属性 key 对应的 dep,即该属性的所有依赖函数。将dep中的所有 effect 添加到一个临时集合 effects 中;
3. 执行副作用函数:遍历 effects,依次执行每个 effect,触发视图更新或回调逻辑;
obj.count++; // 修改 count 时触发 trigger,执行所有依赖 count 的 effect。
4. 关键设计细节
1. WeakMap 的作用:键是弱引用,当目标对象 target 不再被引用时,targetMap 中对应的条目会被自动垃圾回收,避免内存泄漏;
2. activeEffect 的管理:通过 effect 函数或组件渲染过程,将当前运行的副作用函数赋值给 activeEffect。执行完毕后,activeEffect 会被重置为 null,确保依赖收集的准确性;
3. 双向依赖关系:effect.deps 存储了所有与该副作用函数相关的依赖集合dep。当副作用函数被销毁时,可以通过遍历 effect.deps 从所有 dep 中移除该函数,避免无效更新;
4. 性能优化:使用 Set 存储依赖函数,避免重复添加。触发更新时,通过临时集合 effects 确保每个 effect 只执行一次,避免循环触发;
5. 总结
依赖收集(Track):通过 track 函数,在属性被访问时记录当前活动的副作用函数,建立属性与副作用函数的关联。
触发更新(Trigger):通过 trigger 函数,在属性被修改时找到所有相关副作用函数并执行,实现数据变化到视图更新的响应式机制。
全局依赖关系:targetMap 作为核心数据结构,通过 WeakMap 和嵌套的 Map/Set,高效管理对象、属性和副作用函数之间的映射关系。