前言 经过上文的响应式编写之后,实现了数据包裹之后变成了响应式数据,用户修改数据的时候能监听到操作。
但是实际编写的响应式 reactive.ts 中最核心的是 Proxy 中的 get 和 set 方法。为此我们本次需要将核心代码抽离,并且编写 effect 副作用函数和依赖收集功能,这样函数依赖发生改变,他就重新执行。
reactive.ts 核心代码抽离 响应式代码最重要的式 get 和 set 函数,那么对这一块逻辑抽离。并命名为 baseHandles.ts,然后抛出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export  const  enum ReactiveFlags  {  IS_RECEIVE  = `__v_isReactive` , } export  let  baseHandles = {           get (target, key, recevier ) {          return  Reflect .get (target, key, recevier)   },   set (target, key, value, recevier ) {          return  result   }, } 
然后在 reactive.ts 中把抽离的代码引入进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import  { isObject } from  "@vue/shared" ;import  { baseHandles, ReactiveFlags  } from  "./baseHandles" ;const  reactiveMap = new  WeakMap (); export  function  reactive (target: object ) {  if  (!isObject (target)) {     return ;   }      if  (target[ReactiveFlags .IS_RECEIVE ]) {     return  target;   }   let  existingProxy = reactiveMap.get (target);   if  (existingProxy) {     return  existingProxy;   }      const  proxy = new  Proxy (target, baseHandles);   reactiveMap.set (target, proxy);   return  proxy; } 
注意这里出现的一个循环引用逻辑,但是这个不会再 ES6 中造成问题。reactive.ts中引入了baseHandles.ts,但是reactive.ts中对对象的代理包裹,我们应用了reactive.ts,并且包裹了数据,当数据改变的时候,就会触发 set,而这个时候baseHandles.ts又依赖到了reactive.ts。造成了循环引用,但是这个不会导致任何的问题出现。
effect 功能的编写 在测试官方代码的时候,是这么操作 effect 的。effect(() =>{document.getElementById("app").innerHTML = state.name+'今年'+state.age})
通过对上面的操作分析,可以知道通过effect运行了它里面的回调函数,也就是执行了渲染一个谁今年多少岁的一段文字。当被effect包裹的回调函数中state.name和state.age参数改变的时候,我们还要更新下这一段文字。所以有了当前的effect对应这个stage上的 nage 和 age 的映射关系。
再往深处思考,也会出现这种代码effect(() => {stage.name;effect(() => {stage.age})}),这种嵌套的写法,这就是组件的写法了。
上面的实例代码中外层 effect1 可以对应上stage.name,而里面的effect2对应上了stage.age。执行到当前的effect上的时候就能找到对应关联的属性。
所以我们需要这么一个操作,对象的属性->effect,而且如果一个属性可能在多个 effect,那么对象作为 key,最好的是自然就是 WeakMap 了,它还有个好处就是,当 value 为空的时候,垃圾回收机制对它进行回收。
那么对上面对思路进行整合,得出我们需要一个这样对数据结构{对象:Map{name:Set}}。那么现在来编写一下effect.ts,完成里面的依赖收集和属性改变的时候触发再次运行的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 export  let  activeEffect = undefined  class  ReactiveEffect  {     public active = true     public parent = null     public deps = []    constructor (public fn ) {}    run (          if  (!this .active ) {              this .fn ()     }          try  {       this .parent  = activeEffect       activeEffect = this        this .fn ()      } finally  {       activeEffect = this .parent      }   } } export  function  effect (fn ) {     const  _effect = new  ReactiveEffect (fn)    _effect.run ()  } let  targetMap = new  WeakMap ()export  function  track (target, type, key ) {                    if  (!activeEffect) return     let  depsMap = targetMap.get (target)   if  (!depsMap) {     targetMap.set (target, (depsMap = new  Map ()))   }   let  dep = depsMap.get (key)   if  (!dep) {     depsMap.set (key, (dep = new  Set ()))   }   let  shouldTrack = !dep.has (activeEffect)    if  (shouldTrack) {     dep.add (activeEffect)     activeEffect.deps .push (dep)    }                   } export  function  trigger (target, type, key, value, oldValue ) {  const  depsMap = targetMap.get (target)   if  (!depsMap) return     const  effects = depsMap.get (key)   effects &&     effects.forEach ((effect ) =>  {       if  (activeEffect !== effect) effect.run ()                           }) } 
上面的代码除了基础的分析之外,还有对 3.0 初期如何找到当前的 effec 的 2 种做法之外,还解决了模版渲染的时候又触发了 run 函数,导致循环调用的问题。
那么上面的主要代码解释了收集依赖的过程,那么现在要对 baseHandles.ts 做一个依赖收集的入口。
baseHandles.ts编写如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import  { isObject } from  "@vue/shared" ;import  { baseHandles, ReactiveFlags  } from  "./baseHandles" ;const  reactiveMap = new  WeakMap (); export  function  reactive (target: object ) {  if  (!isObject (target)) {     return ;   }      if  (target[ReactiveFlags .IS_RECEIVE ]) {     return  target;   }   let  existingProxy = reactiveMap.get (target);   if  (existingProxy) {     return  existingProxy;   }      const  proxy = new  Proxy (target, baseHandles);   reactiveMap.set (target, proxy);   return  proxy; } 
结尾 到目前为止,完成了所需要做的工作,这个时候可以在index.html上引入编写好的effect功能,先运行项目
npm run dev,然后倒入编译好的文件。从对象中获取编写的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <!DOCTYPE html > <html  lang ="en" >   <head >      <meta  charset ="UTF-8"  />      <meta  http-equiv ="X-UA-Compatible"  content ="IE=edge"  />      <meta  name ="viewport"  content ="width=device-width, initial-scale=1.0"  />      <title > Document</title >    </head >    <body >           <div  id ="app" > </div >                     <script  src ="./dist/reactivity.global.js" > </script >      <script >                             const  { effect, reactive } = VueReactivity ;       let  target = { name : "david" , age : 12 , address : { num : 567  } };       const  state = reactive (target);       const  state2 = reactive (target);       console .log (state === state2);              effect (() =>  {         state.age  = Math .random (10 );         document .getElementById (           "app"          ).innerHTML  = `${state.name} 今年${state.age} ` ;       });       setTimeout (() =>  {         state.age  = 13 ;       }, 1000 );      </script >   </body >  </html > 
1 git:[@github/MicroMatrixOrg/vue3-plan/tree/effect_schedule]