响应式依赖和更新是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,高效管理对象、属性和副作用函数之间的映射关系。