Vue3 的响应式系统嵌套 effect 的实现原理与清理机制
字数 1147 2025-11-07 22:15:36
Vue3 的响应式系统嵌套 effect 的实现原理与清理机制
1. 问题描述
在 Vue3 的响应式系统中,effect 可能会嵌套执行(例如组件渲染时触发了另一个 effect)。若不正确处理嵌套依赖,会导致依赖收集混乱(如父 effect 依赖了子 effect 的属性)。Vue3 通过 effect 栈 和 activeEffect 切换机制 解决此问题。
2. 核心概念:effect 栈与活跃 effect
- effect 栈(effectStack):用于存储正在执行的
effect函数,按执行顺序入栈/出栈。 - activeEffect:当前正在处理的活跃 effect,依赖收集时关联到该变量。
3. 嵌套 effect 的执行流程
假设如下代码:
effect(() => {
console.log("父 effect 执行");
effect(() => {
console.log("子 effect 执行");
});
});
步骤 1:初始化执行
- 外层 effect 被推入 effect 栈,
activeEffect指向父 effect。 - 开始执行父 effect 的函数体。
步骤 2:内层 effect 执行
- 遇到内层 effect 时,先将父 effect 的执行暂停,内层 effect 入栈,
activeEffect切换为子 effect。 - 执行子 effect 的函数体,收集子 effect 的依赖。
步骤 3:内层执行完毕
- 子 effect 执行结束,出栈,
activeEffect恢复为父 effect。 - 继续执行父 effect 的剩余代码。
关键点:
通过栈结构保证 activeEffect 始终指向当前正在执行的 effect,避免依赖收集错乱。
4. 依赖清理机制(避免遗留依赖)
当 effect 重新执行时,需要先清理旧依赖,再收集新依赖。Vue3 通过 deps 数组实现反向清理。
步骤 1:依赖记录
每个 effect 实例维护一个 deps 数组,存储所有关联的依赖集合(即响应式属性对应的 Set)。
步骤 2:清理旧依赖
effect 重新执行前,遍历 deps,从每个依赖集合中删除该 effect,避免遗留无效依赖。
步骤 3:重新收集
执行 effect 函数时,依赖被重新添加到最新的依赖集合中。
5. 源码级示例(简化版)
let activeEffect;
const effectStack = [];
class ReactiveEffect {
constructor(fn) {
this.fn = fn;
this.deps = []; // 存储依赖集合
}
run() {
// 清理旧依赖
cleanupEffect(this);
// 压栈并切换活跃 effect
effectStack.push(this);
activeEffect = this;
// 执行函数(触发依赖收集)
this.fn();
// 出栈并恢复上一个 effect
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
function cleanupEffect(effect) {
for (const dep of effect.deps) {
dep.delete(effect); // 从依赖集合中移除该 effect
}
effect.deps.length = 0; // 清空 deps 数组
}
6. 实际场景:组件渲染嵌套
- 父组件渲染时触发父 effect,子组件渲染触发子 effect。
- 通过 effect 栈确保子组件依赖正确关联到子 effect,而非父 effect。
7. 总结
- 嵌套 effect:通过栈结构管理
activeEffect,保证依赖收集准确。 - 依赖清理:通过
deps反向关联,避免无效依赖残留。 - 性能优化:避免不必要的依赖关联,提升响应式系统效率。