Vue3源码系列之依赖收集的实现

effect
effect方法是响应式的核心,叫做副作用函数。初始会默认执行时会进行取值操作,只要取值就会调用get方法,我们就可以将对应的effect函数存放起来,等会我们去更新effect函数内部所依赖的状态数据时,就去调用一开始存对应的effect函数,视图就进行了更新
 
注:state与effect有关联,只有对应的数据变了,对应的effect才会执行
 
核心原理
 
第一步
创建effect.ts文件,导出effect方法,内部依靠一个创建响应式的方法createReactiveEffect,外层做格式化,内层做响应式。
注:内部返回的effect是一个函数
 
根据第二个参数选项项中的lazy属性判断,如果不是非trueeffect默认会先执行一次
 
 
第二步
开始编写createReactiveEffect的逻辑
 
  • 我们需要给effect加一个唯一标识,方便追踪这是哪个地方属于谁的的effect
    • 在外面做一个序号uid,每次调用createReactiveEffect,就给创建出来的effect加一个标识id,值为uid++
  • 我们需要再给effect加一个_isEffect标识,用于标识这个是响应式effect,值为true,下划线字段标识私有的
  • 我们还需要记录一下effect对应的原本的函数是谁,raw = fn
  • 我们再把选项也保存起来
       
 
 
第三步
刚才我们说到,effect会根据选项初始默认执行一次,那么我们先在reactiveEffect函数里让fn执行一次,也就是用户传入的函数
 
 
用户调用的时候effect的时候会进行取值,那一取值就会触发该属性的get方法,走到对应的hanlders里。之前我们说到,vue3的代理模式的惰性的。所以我们在此时进行依赖收集,收集effect
 
第三步
在reactive里进行依赖收集,也就是回到上一篇文章的最后地方。
 
首先我们在createrGetter里写一个track方法进行依赖收集,而这个track方法写在了effect文件里,将方法导出,reactive文件 里进行引入
 
  • 我们需要告诉tract哪个对象,什么操作?哪个属性
  • 新建一个文件operators.ts专门用于放操作符的
    •    
 
 
现在我们去写track
问题:我们怎么在track里拿到对应的effect呢?
 
答案就是全局变量,利用全局变量就关联起来了
 
问题一:我们可能写出这样的代码
它会取最后一个,这就乱了,去address,effect应该还是effect1

所以我们可以弄栈型结构来处理,来保证每个属性收集的effect是正确的
 
每次执行的时候入栈,然后把当前的effect保存到全局变量,然后调用用户传的函数,最后执行完了,再把effect出栈,然后取当前栈中最后一个赋给全局变量,也就是执行完再赋值回去。
 
这样操作就保证了effect顺序,在track中取到的effect是正确的
 
问题二:我们可能写出这样的代码
就死循环了,会不停的刷新
 
所以我们在放入栈的时候,先看看这个effect是不是已经在栈里了
 
综上:我们就实现了让某个对象中的属性收集当前他对应的effect函数
 
第四步
一个对象的属性是有可能对应多个effect的,所以我们现在要用一种数据结构给他俩关联起来
我们可以用weakMap来进行关联
 
在外面new 一个WeakMap
 
如果activeEffect等于undefined,则表示没在effect中使用
 
然后进行关联
 
解析:
  • 先去target里取值,第一次取肯定是undifined
  • 如果没取到,那我就往这里面放,key是这个target,第二次参数就是个map,这个新new的map同时又赋值给当前的depsMap
  • 再从targetMap进行取值,看看没有依赖收集
  • 如果没有,往当前的map里设置进去,进行依赖收集,值设置成一个set
  • 如果当前取的set中没有当前的activeEffect,那就往当前的set中把依赖添加进去,由此可见,依赖是收集在了最后的set数据结构中
  • 有点绕,最后映射出来的结构大概是这个样子
 可能有人有对用weakMap和map没啥疑问,但是对最后的set数据解决进行存储effect有疑问,为啥最后那里要用set来存储effect呢?
  • 答案如果你在effect内对一个对象取了多次,那么如果没有用set,这个属性你就保存了多个相同的effect,是这个道理吧
posted @ 2021-09-10 16:06  Godfi  阅读(423)  评论(0编辑  收藏  举报