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

156 阅读2分钟

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

从这一章开始,难度陡增了。我尽量用通俗的语言表达书中的主要内容,示例代码尽量也用我自己的理解,如有错误,欢迎留言

4.4 分支切换与cleanup

什么是分支切换?

我相信大部分人都写过这种代码

const data = reactive({login:false,text:''})
const effectFn = ()=> data.login ? data.text : 'please login'
const text = computed(effectFn)

computed再加上三目运算符很好用,可以作条件的切换.

但是对于我们之前写的reactive那个方法就有问题了。

首先,effectFn这个函数中使用到了,当我们读取data.login和data.text时,都会收集到effectFn.

其次,effectFn中其实只在data.login === true时才有用,当data.false === false时,其实无需追踪data.text.

最后effectFn里其实主要依赖的是data.login,data.text的变化完全不会也不需要引起副作用函数的执行。

但是在我们之前实现的reactive中,读取任意属性都会被添加依赖。这样就容易造成副作用函数的无用执行,执行次数过多。

书中是这样解决的,执行副作用函数时,先把它从所有与之关联的依赖集合中删除。然后执行完再把它添加回去,这样就能保证只执行一次副作用函数

要想将一个副作用函数从所有与之关联的的依赖集合中删除,就需要明确知道那些依赖集合中包含了它。因此我们需要修改副作用函数,给他添加一个属性,用来保存所有包含当前副作用函数的依赖集合。

let activeEffect
const effect = (fn)=>{
  const runEffect = ()=>{
      //这里需要cleanup
      activeEffect = runEffect
      fn()
  }
  runEffect.deps = []
  runEffect()
}

那么,这个runEffect.deps是在什么时候使用呢,其实是在track的时候

const track = (target,key)=>{
    if (!activeEffect) { return; } 
    let depsMap = targetsMap.get(target); 
    if (!depsMap) { targetsMap.set(target, (depsMap = new Map())); } 
    let dep = depsMap.get(key); 
    if (!dep) { depsMap.set(key, (dep = new Set())); } 
    dep.add(activeEffect);
    //新增这句
    activeEffect.deps.push(dep)
}

这样,我们就收集到了这个副作用函数与之关联的依赖,因此我们就可以写出一个cleanup函数

const cleanup = (effectFn)=>{
    for(let i=0;i<effectFn.deps.length;i++){
        const deps = effectFn.deps[i]
        //这里取到的其实是set
        deps.delete(effectFn)
    }
    //清楚后,重置
    effectFn.deps=[]
}

cleanup会遍历副作用中的依赖集合,删除自身,最好重置依赖集合。但是如果你运行这段代码,你会发现一个新的问题,我们下一篇再看。