依赖收集的实现原理-详细步骤

1,096 阅读5分钟

我们的目的的让属性和函数产生关联

image.png

  • 我们写这个类的原因是为了扩展这个函数的功能,进而在属性变化的时候,除了重新执行函数之外,还可以做一些其它的事情,比如重新渲染
  • effect方法会在初始的时候调用一次,当调用的时候,会对具备响应式的值进行取值操作,那么就会触发该值上的get方法,我们就可以在get方法上拿到对应的activeEffect,然后我们就可以把属性key和activeEffect关联在一起

问题1:

image.png

  • 第一次取state上的name的时候,能收集到e1,然后取内层的state的age的时候,age会收集到e2,然后内层effect的函数执行完后,我们就把activeEffect置为undefined了,那么最后取state上的 address的时候,它的get里拿到的activeEffect就是undefined了

解决问题1:

  • 老版本就是用栈解决的,但是栈比较消耗性能,新版本类似一个树形结构

新版执行流程:

  • 我们在effect上都增加一个属性parent,最外层的effect上的parent就是null
  • 当我们执行下一个effect的时候,它的parent就是e1,age收集的就是e2
  • 当我们取state上的address的时候,再把this.parent赋值给activeEffect就可以了
  • 简而言之就是,当前自己的effect执行完了,就把自己的父节点赋值给activeEffect就解决了

在ReactiveEffect类里增加一个parent属性,ReactiveEffect就是effect参数的包装类

image.png

然后在调用fn之前,把当前的activeEffect赋值给当前实例的parent上

image.png

然后在fn执行之后再还原回去

image.png

下面就是让get中拿到的activeEffect和key进行关联

我们就写一个方法去进行关联,也就是依赖收集

image.png

  • 一个属性可以对应多个effect,上面我们的例子有体现到
  • tract收集哪个对象的哪个属性,是get的时候收集的
  • 也就是一个对象对应的某个属性,属性是个数组,数组里保存着它所有的effect,那么用WeakMap是非常合适的。{对象:{name:[]}}
  • 对象的值又像一个map,那么我可以用map来存,key存的数组里的值,我们可以再去一次重,使用Set,那么最后的结构就是 WeakMap:{对象:Map(Set())}

下面我们来写这个track方法,在effect.ts文件里中

  • 参数就是target,type,和key
  • 我们不能所有的都收集,如果在使用的时候,不在effect里进行取值,而是在外面,那么我们就不用收集

image.png

  • 如果activeEffect没有值,我们就直接return出去,不去依赖收集

首先我们先创建一个WeakMap

image.png

我们先看一下,有没有存放过这个对象,第一次没有,如果没有,那么我们就把这个对象放进WeakMap

image.png

  • 存入的同时,赋值给depsMap,既存入又赋值

然后我们看对象的值map里有没有key属性,很明显第一次没有,第一次赋值了一个空的Map,如果没有我们继续放

image.png

然后我们需要再判断要不要收集,因为可能这个key里已经存放过当前的activeEffect了,如果判断key的值Set里没有当前activeEffect,那么我们就把它放进Set中

image.png

问题1:

  • 当前我们是单向记录,只是属性记录了effect,我们还应该让effect也记录它被哪些属性收集过这样做的好处是为了可以清理

那么哪些时候我们需要清理呢?

image.png

  • 像这种三元动态收集的时候,flag可能会进行变化,当它变化的时候我们就需要收集另一个属性,而释放掉当前属性收集的effect

我们需要去给每个activeEffect上加一个属性deps,来记录当前activeEffect收集了哪些属性,双向记忆

image.png

然后我们再回到track函数中,让当前的activeEffect也去记录对应的属性

image.png

  • 稍后清理的时候会用到
  • deps存的是属性的值,是个Set。不存key的原因是,存个key没用,存key还得去找一下它对应的值,还不如直接存它的值

到此,依赖收集就完成了

当我们去更改响应式的值的时候,会触发set

  • 我们在修改的时候,有可能修改的值跟老值一样,那么我们需要判断,如果老值跟新值不一致,我们要去更新,否则我们只设置不更新

image.png

现在我们来写trigger方法,在effect.ts中

首先我们去targetMap里找有没有存过当前target对象

  • 如果没有,我们就不用更新

image.png

如果有,那么我们就用这个属性key去取那个保存着那些effect的Set

image.png

  • 拿到Set后,把这个属性关联的那些effect全部执行

问题1:

有可能我们在执行effect的时候,又要执行自己,会死循环

  • 我们应该屏蔽掉,如果当前正在执行的effect不是自己的时候,我们在执行自己
  • 全局变量activeEffect保存着当前正在执行的effect

image.png

image.png

  • effect再更改响应式的值,又会调trigger,这个速度是非常快的,所以你再次调的时候可能activeEffect就是自己,这个时候就屏蔽掉,避免死循环