Vue3.0源码系列(一):响应式原理 (effect,reactive,依赖收集get,触发依赖set)

488 阅读4分钟

下一篇:Vue3.0源码系列(二):响应式原理(实现effect内部的runner和scheduler)

文章背景:此文章是基于mini-vue项目进行,通过学习源码,个人对vue3.0的源码的一点理解和学习的记录,文章中可能有理解不到位或者错误的地方,第一次发掘金,希望大家理解,多多指正哈。

欢迎大家去star和学习哈。 本人github:github.com/zzq921/my-m… 欢迎大家star

一:实现effect

1.首先我们创建一个effect的单元测试,然后根据单元测试实现我们的effect的代码逻辑,我们给effect方法传一个fn方法,方法里面执行对nextAge变量的赋值,我们期待nextAge的值变为11

     //1.创建effect的单元测试
    describe("effect",()=>{
      it('effect逻辑测试',()=>{
        const user = reactive{(
          {
            age:10
          }
        )}
        let nextAge;
        effect(()=>{
          nextAge = user.age + 1
        })
        // 单元测试(1)
        expect(nextAge).toBe(11)
        user.age++
        // 单元测试 (2)
        expect(nextAge).toBe(12)
    })
    
    //2.创建effect方法

   export function effect(fn) {
      // 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
      return new ReactiveEffect(fn)
   }
   
   
   export class ReactiveEffect{
      constructor(fn) {
        //接受fn方法
        this._fn = fn  
      }
      //每次effect被调用时候,执行run方法,进而执行fn()方法
      run() {
        this._fn()
      }
     
    }

二.实现reactive

2.创建reactive方法和他的单元测试,1.期待包裹前后是不想等的 2.期待对象取值为10

//通过reactive方法包裹,
describe('reactive', () => {
  it('happy path',()=>{
    const original = {age:10}
    const observe = reactive(original)
    expect(observe).not.toBe(original)
    expect(observe.age).toBe(10)

  })
})
//reactive实质就是通过Proxy实现对象数据的响应式监听,get方法做依赖收集,set方法做依赖触发
function reactive(raw: any) {
  //
  return new Proxy(raw, {
      get(target,key) {
          const res = Reflect.get(target,key)
          //这里需要做依赖收集track方法
          //track(target,key)
          return res
      },
      set(target,key,value) {
          const res = Reflect.get(target,key,value)
          //这里做依赖的触发
          //trigger(target,key,value)
          return res
      }
  })
}

通过reactive方法的实现,我们就通过了reactive的单元测试,这样我们就实现了简单的reactive方法,就可以把方法在effect的单元测试中进行引用。

3.我们在ReactiveEffect类中抽离出run方法,来执行用户传过来的fn方法。这是我们就实现了 effect单元测试(1)的方法,

 export function effect(fn) {
     const _effect = new ReactiveEffect(fn)
     _effect.fn()
 }
export class ReactiveEffect{
     constructor(fn) {
         //接受fn方法
         this._fn = fn  
     }
     //每次effect被调用时候,执行run方法,进而执行fn()方法
     run() {
        return this._fn()
     }

}

4.接下来我们在reactive中进行依赖收集track函数。

let activeEffect; // 当前effect
export function track(target,key) {
  //targetMap -- depsMap -- dep
  //通过目标对象的target获取 depsMap对象,此对象为map结构 key-dep
  let depsMap = targetMap.get(target)
  //初始化时候depsMap不一定存在,所以要进行判断
  if(!depsMap) {
    //不存在初始化一个depsMap对象,
    depsMap = new Map()
    //设置目标target为key,新建的depsMap为value,targetMap为存储map容器
    targetMap.set(target,depsMap)
  }
  //当depsMap存在时候,通过key取到value对象
  let dep = depsMap.get(key)
  //如果dep容器不存在或者没有取到值
  if(!dep) {
    //新建一个dep对象,此对象结构为set结构。
    dep = new Set()
    // 属性值作为key,effect的集合dep作伪value,存储到depsMap的集合中。
    depsMap.set(key,dep)
  }
  //如果dep存在,把dep当作参数传入,进行依赖收集。封装成一个trackEffects方法,方便后续进行复用。
  trackEffects(dep)
}


 export function trackEffects(dep) {
  //此dep为所有与key有关的effect集合,收集到set结构的dep中。       
  //dep = [effect,effect,effect,effect]
  if(dep.has(activeEffect)) return
  dep.add(activeEffect)
}

5.最后我们在接下来我们在reactive中进行依赖收集trigger函数

export function trigger(target,key) {
  //通过目标值target,依赖的key值,获取所有与此依赖有关的集合dep
  let depsMap = targetMap.get(target)
  let dep = depsMap.get(key)
  //触发依赖的方法,把dep当作参数传入,封装成一个triggerEffects方法,方便后续进行复用
  triggerEffects(dep)
}

export function triggerEffects(dep) {
  //循环set结构的dep数组
  for(let effect of dep) {
      //取出每一个effect,直接进行依赖触发,执行run方法,run方法里会调用fn
      effect.run()

  }
}

通过依赖收集track和触发依赖的trigger方法,我们就完成了单元测试 (2)的单元测试,

总结:vue3.0的响应式其实就是通过reactive方法中的new proxy(),对数据进行监听,通过get方法进行依赖收集,把所有依赖的effect放到一个容器 dep中。当依赖变化时,触发set,执行trigger方法,遍历dep,取出所有effect来进行执行。说的可能有欠缺的地方,第一次写掘金,不喜勿喷哈。