精读《Vuejs设计与实现》(18)之响应系统8

267 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

4.5嵌套的effect和effect栈

其实effect内部还会嵌套effect,最典型的一个场景就是组件的渲染。 我们现在又这样的一个组件

<template>
<Foo>
    <Bar />
</Foo>
</template>

我们把它翻译为副作用函数就相当于

effect(()=>{
    render(Foo)
    effect(()=>{
        render(Bar)
    })
})

这说明effect应该被设计成允许嵌套的。但是如果你用我之前的代码跑一下,你会发现一个问题,嵌套最里面哪个副作用函数并不会执行。这之前以为,不可能会在同一时间调用effect2次,但是现在情况是嵌套的,也就是说可能会在同一个函数中对activeEffect赋值2次,这样势必会丢失一次赋值。拿上面的例子来讲,Bar会覆盖掉Foo,这时候Bar的响应式就丢失了,且永远不会再次赋值。这样很明显就时错的。

这时候,我们需要引入一个新的数据结构:栈

(stack)是允许在同一端进行插入和删除操作的特殊线性表。 允许进行插入和删除操作的一端称为顶(top),另一端为底(bottom);底固定,而顶浮动;中元素个数为零时称为空。 插入一般称为进(PUSH),删除则称为退(POP)。 也称为先进后出表。

在js中并没有栈这个对象,但是我们可以通过数组去模拟。

我们在全局再新建一个变量effectStack

let activeEffect
const effectStack = []
const effect((fn)=>{
    const runEffect=()=>{
        cleanup(runEffect) // 清除重复依赖
        activeEffect = runEffect
        effectStack.unshift(runEffect) //将副作用函数压入栈中
        fn()
        effectStack.pop() // 执行完成后,出栈
        activeEffect = effectStack.at(-1) //将activeEffect对准栈底
    }
    runEffect.deps=[]
    runEffect()
})

我们定义了一个effectStack来模拟栈,activeEffect没有变化,依旧只想当起执行的副作用函数。不同的是,我们会把当前执行的副作用压入栈顶(原书代码中用了push,但我觉得应该是unshift,要不然就不是先进后出了)。这样当副作用发生嵌套时,栈底就是最外层嵌套的。当最内层执行完,会被出栈,而activeEffect则会执行外层的副作用函数。

原书这里应该有点错误,大家看到这里也可以想一想,看是不是出错了