Vue3.0源码系列(三):响应式原理(stop和onStop原理实现)

589 阅读4分钟

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

这一篇为大家介绍源码中stop和onStop两个方法的实现,还是老规矩,如果我们想实现功能,首先书写单元测试,基于单元测试,开发功能逻辑,保证各项单元测试case全部通过,最后对书写的代码进行重构(TDD思想),不喜勿喷哈。

stop功能的实现:

1.首先,书写stop的单元测试,运行stop方法,参数时我们的effect的返回值,当我们响应式数据发生变化时候,我们发现dummy值并没有改变,也就是说源码中stop方法阻止了effect的依赖触发。当我们手动执行runner时候,effect依赖重新执行,数据发生了变化。

it('stop',()=>{
    let dummy;
    const obj = reactive({prop:1})
    const runner= effect(()=>{
      dummy = obj.prop
    })
    obj.prop = 2
    expect(dummy).toBe(2)
    stop(runner)
    obj.prop++
    expect(dummy).toBe(2)
    runner()
    expect(dummy).toBe(3)
 })
 

2.那么我们来一点一点实现源码中的stop功能,看看他到底是一个什么东东,如此神秘那

 export function stop(runner) {
  // runner就是一个effect实例,指向ReactiveEffect中的的stop方法
  runner.effect.stop()
}


export class ReactiveEffect{
  private _fn:any //内部的_fn()方法
  deps = []
  active = true // avtive 属性控制stop方式执行的状态,stop执行其变为false
  //public scheduler 外部能够获取到scheduler方法
  constructor(fn) {
    //接受fn方法
    this._fn = fn  
  }
  //每次effect被调用时候,执行run方法,进而执行fn()方法
 
  stop() {
    //当this.active为true时,代表stop第一次执行。
    if(this.active) {
      //清除所有的effect
      cleanupEffect(this)
      //执行之后变为false,防止重复调用,优化性能。
      this.active = false
    }

  }
}


function cleanupEffect(effect) {
  //取出effect中反向收集到的deps,循环删除dep中的effect
  effect.deps.forEach((dep:any)=>{
    dep.delete(effect)
  })
  effect.deps.length = 0
}


export function effect(fn,options:any = {}) {
  // 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
  const _effect = new ReactiveEffect(fn,options.scheduler)
  //利用extend方法实现effect的第二个参数options的合并。
  extend(_effect,options)
  _effect.run()
  //处理_effect指针,当前的实例的run方法
  const runner:any = _effect.run.bind(_effect)
  //将当前_effect挂载到runner的effect上,方便stop方法获取。
  runner.effect = _effect
  //返回一个runner函数
  return runner
}

通过源码中我们可以看到,stop方法的底层实现其实就是,把runner执行所收集到的所有effect从deps中删除,然后关上active开关,当响应式数据再发生变化的时候,deps中的effect都被删除,所以不会触发set。为了进行代码性能优化,当重复调用stop时候,因为有active开关,也只会调用一次stop。

onStop功能实现

1:onStop方法其实就是stop方法的回调,通过effect的第二个参数传进去类似effect(fn1,fn2),当我们执行完stop函数后,onStop会在之后进行执行,下面是onStop方法的单元测试。

it('onStop',()=>{
    const obj = reactive({
      foo:1 
    })
    const onStop = jest.fn()
    let dummy;
    const runner = effect(
      ()=>{
        dummy = obj.foo
      },
      {
        onStop
      }
    )
    stop(runner);
    //单元测试(1)
    expect(onStop).toBeCalledTimes(1);
  })
  

2.那么我们就来看看onStop的源码怎么实现那,在stop方法执行完后,通过判断方法this.onStop,来执行回调this.onStop()方法,这是我们看到单元测试(1)被执行了1次,证明onStop方法被调用了。

 export function effect(fn,options:any = {}) {
  // 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
  const _effect = new ReactiveEffect(fn,options.scheduler)
  //利用extend方法实现effect的第二个参数options的合并,extend方法就是封装的 object.assign,把传过来的onStop参数进行合并。
  extend(_effect,options)
  _effect.run()
  //处理_effect指针,当前的实例的run方法
  const runner:any = _effect.run.bind(_effect)
  //将当前_effect挂载到runner的effect上,方便stop方法获取。
  runner.effect = _effect
  //返回一个runner函数
  return runner
}



export class ReactiveEffect{
  private _fn:any //内部的_fn()方法
  deps = []
  active = true // avtive 属性控制stop方式执行的状态,stop执行其变为false
  onStop?:()=>void //可选方法用?: 声明
  constructor(fn) {
    //接受fn方法
    this._fn = fn  
  }
  stop() {
    //当this.active为true时,代表stop第一次执行。
    if(this.active) {
      //清除所有的effect
      cleanupEffect(this)
      //当stop方法之后执行之后,onStop存在则会被执行,onStop为用户传入的第二个参数,执行用户传入的回调,也就是stop方法的回调
      if(this.onStop) {
        this.onStop()
      }
      //执行之后变为false,防止重复调用,优化性能。
      this.active = false
    }

  }
}

通过上面stop和onStop的源码分析,相信你会对他们有一个更加深入的了解,以后我们在vue3应用中,会更加得心应手,持续更新中......