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

673 阅读3分钟

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

背景:接着上次文章一juejin.cn/post/708303… 往下看,这一节我们实现runner的功能,其主要就是当我们调用effect方法之后,代码会返回一个function函数,当调用runner函数之后,会再次执行传给effect方法的fn方法,进而拿到fn函数的返回值。

崔大大:mini-vue项目地址:github.com/cuixiaorui/…

个人学习github:github.com/zzq921/my-m…

1.书写runner功能的单元测试

it('当调用effect时候,会返回一个runner方法',()=>{
    let foo = 10
    const runner:any = effect(()=>{
      foo++
      return 'foo'
    })
    expect(foo).toBe(11)
    const r = runner()
    //单元测试(1)
    expect(foo).toBe(12)
    //单元测试(2)
    expect(r).toBe('foo')
 })
 
 

2.在effect中实现runner功能

   export function effect(fn) {
      // 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
      const _effect = new ReactiveEffect(fn)
      //处理_effect指针,当前的实例的run方法
      const runner:any = _effect.run.bind(_effect)
      //返回一个runner函数 ,完成 单元测试(1)
      return runner
    }
   
    export class ReactiveEffect{
      
      constructor(fn) {
        //接受fn方法
        this._fn = fn  
      }
      //每次effect被调用时候,执行run方法,进而执行fn()方法
      run() {
        //this代表当前effect 赋值给activeEffect
        activeEffect = this
        const result = this._fn();
        //return 出返回值 完成 单元测试(2)
        return result
      }
    
    }
    
    

3.实现scheduler,scheduler为我们调用effect时候传入的第二个参数,一般会执行一些我们编码当中的一些逻辑,当我们响应式对象放生变化时,也就是说set的时候,会执行scheduler函数,而不会再去执行effect当中的第一个参数fn方法。

 it('scheduler',()=>{
    let dummy;
    let run:any;
    const scheduler = jest.fn(()=>{
      run = runner;
    })
    const obj = reactive({foo:1})
    const runner = effect(
      ()=>{
      dummy = obj.foo
      },
      {
        scheduler
      }
    )
    //单元测试(1)
    // 执行effect的第一个参数fn,第二个参数scheduler方法没有被执行
    expect(scheduler).not.toHaveBeenCalled()
    expect(dummy).toBe(1)

    obj.foo++
    //单元测试(2)
    //当响应式数据变化 obj.foo++,也就是set时候,执行scheduler方法,但是不会执行fn。
    expect(scheduler).toHaveBeenCalledTimes(1)
    expect(dummy).toBe(1)
    
    run()
     //单元测试(3)
     //当我们调用run也就是runner方法时候,会再次执行fn
    expect(dummy).toBe(2)
  })
  
  

4.实现远源码中的scheduler方法

  export function effect(fn,options:any = {}) {
      // 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
      const _effect = new ReactiveEffect(fn,options.scheduler)
      //处理_effect指针,当前的实例的run方法
      const runner:any = _effect.run.bind(_effect)
      //返回一个runner函数
      return runner
   }
   
   
   
   export class ReactiveEffect{
      private _fn:any //内部的_fn()方法
       //public scheduler 外部能够获取到scheduler方法
      constructor(fn,public scheduler?) {
        //接受fn方法
        this._fn = fn  
      }
      //每次effect被调用时候,执行run方法,进而执行fn()方法
      run() {
        const result = this._fn();
        //return 出返回值
        return result
      }
      
    }
    
    

5.在trigger中也就是响应式数据发生变化,也就是set时候进行判断,如果scheduler存在,则执行scheduler()方法。否则执行fn()方法

 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,判断effect是否有第二个参数传入
    if(effect.scheduler) {
      //如果存在,调用scheduler方法,也就是用户传过来的第二个回调函数fn2。effect(function fn1(){},function fn2(){})
      effect.scheduler()
    }else{
      //如果没有第二个参数,直接进行依赖触发,执行run方法,run方法里会调用fn
      effect.run()
    }

  }
}

这节主要分析了vue3源码中的runner和scheduler的实现方法,日常开发中scheduler还是很有用处的,当我们应用时,要懂得内部的原理实现。其实内部实现超级简单就是对参数进行了判断,来执行传入内部的方法,好了,今天的源码分析就到这里,期待下篇文章见。