4.5 嵌套的 effect 与 effect 栈
用一个全局变量记录「当前副作用函数」无法支持嵌套;
effectStack让activeEffect始终指向栈顶,从而正确恢复外层上下文。
一、为什么会嵌套
let temp1, temp2
effect(function effectFn1() {
console.log('effectFn1 执行')
effect(function effectFn2() { // 嵌套 effect
console.log('effectFn2 执行')
temp2 = obj.bar // 在 effectFn2 中读 obj.bar
})
temp1 = obj.foo // 在 effectFn1 中读 obj.foo
})
组件渲染时 render 本身就是一个 effect,而组件嵌套(父组件渲染子组件)天然产生嵌套 effect,所以这个问题必须解决。
二、问题:activeEffect 被覆盖且不会恢复
activeEffect是全局唯一的,同一时刻只能保存一个副作用函数;- 内层副作用函数执行时把
activeEffect覆盖为effectFn2,执行完毕后不会自动恢复; - 于是
temp1 = obj.foo触发track时收集到的是effectFn2→obj.foo的依赖被错绑到内层函数上。
结果:修改 obj.foo 没有任何反应,修改 obj.bar 反而让外层函数更新。
三、解决:effect 栈
let activeEffect
// effect 栈
const effectStack = []
function effect(fn) {
const effectFn = () => {
cleanup(effectFn)
// 当前副作用函数压栈
activeEffect = effectFn
effectStack.push(effectFn)
fn()
// 执行完毕弹栈,并把 activeEffect 还原为栈顶(即外层副作用函数)
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
effectFn.deps = []
effectFn()
}
- 发生嵌套时栈底是外层函数、栈顶是内层函数,
activeEffect永远指向栈顶; - 内层弹栈后
activeEffect自动回到外层函数,后续读取被正确收集到外层。
四、易错点
弹栈后一定要写
activeEffect = effectStack[effectStack.length - 1];只pop()不还原,activeEffect会停留在已经执行完的函数上。 栈为空时effectStack[-1]得到undefined,正好等价于「当前不在任何副作用函数中」,与track里的if (!activeEffect) return呼应。
相关
- 4.4 分支切换与 cleanup:
cleanup与遍历副本 - 4.6 避免无限递归循环:只有一个
activeEffect还会引出另一个问题