12.优化stop

114 阅读2分钟

一、单元测试编写

前面实现effectde stop功能中,对于以下obj.prop++的情况无法满足停止触发依赖。测试用例如下

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 = 3;
    // obj.prop++ 可以拆分为obj.prop = obj.porp + 1,涉及到一个get和set
    obj.prop++
    expect(dummy).toBe(2);
    // stopped effect should still be manually callable
    runner();
    expect(dummy).toBe(3);
  });

以上测试会报错,原因是opj.prop++可以拆分为obj.prop = obj.prop + 1,涉及到一个get和set,之前的只涉及到set操作。而get操作会重新收集依赖。导致之前stop的清理依赖无效。

二、功能修改

我们可以在原来基础上新增一个shouldTrack来决定在执行tack的时候是否应该收集依赖。 原来实现如下:

import {extend} from '../shared'
class ReactiveEffect{
  private _fn:any;
  deps = [];
  active = true;
  onStop?:() => void;
  constructor(fn, public scheduler?){
    this._fn = fn;
  }
  run(){
    activeEffect = this;
    return this._fn();
  }

  stop(){
    if(this.active){
      cleanupEffect(this);
      if(this.onStop){
        this.onStop();
      }
      this.active = false;
    }
    cleanupEffect(this);
  }
}
function cleanupEffect(effect){
  effect.deps.forEach((dep: any) => {
    dep.delete(effect);
  })
}

const targetMap = new Map();
export function track(target,key){
  // target -> key -> dep
  let depsMap = targetMap.get(target);
  if(!depsMap){
    depsMap = new Map();
    targetMap.set(target,depsMap);
  }
  let dep = depsMap.get(key);
  if(!dep){
    dep = new Set();
    depsMap.set(key,dep)
  }
  if(!activeEffect) return;
  dep.add(activeEffect);
  activeEffect.deps.push(dep);
}
export function trigger(target,key){
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);
  for(const effect of dep){
    if(effect.scheduler){
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

let activeEffect;
export function effect(fn, options: any= {}){
  const _effect = new ReactiveEffect(fn,options.scheduler);
  // options
  Object.assign(_effect,options);
  // extend
  extend(_effect,options);
  _effect.onStop = options.onStop;
  _effect.run();
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner
}

export function stop(runner){
  runner.effect.stop()
}

修改后的代码如下:

import {extend} from '../shared'
//新增
let activeEffect;
//新增
let shouldTrack;
class ReactiveEffect{
  private _fn:any;
  deps = [];
  active = true;
  onStop?:() => void;
  constructor(fn, public scheduler?){
    this._fn = fn;
  }
  run(){
    if(!this.active){
      return this._fn()
    }
    shouldTrack = true;
    activeEffect = this;
    const result = this._fn()
    // reset
    shouldTrack = false;
    return result;
  }

  stop(){
    if(this.active){
      cleanupEffect(this);
      if(this.onStop){
        this.onStop();
      }
      this.active = false;
    }
    cleanupEffect(this);
  }
}
function cleanupEffect(effect){
  effect.deps.forEach((dep: any) => {
    dep.delete(effect);
  })
  effect.deps.length = 0;
}

const targetMap = new Map();
export function track(target,key){
  if(!isTracking()) return;
  // target -> key -> dep
  let depsMap = targetMap.get(target);
  if(!depsMap){
    depsMap = new Map();
    targetMap.set(target,depsMap);
  }
  let dep = depsMap.get(key);
  if(!dep){
    dep = new Set();
    depsMap.set(key,dep)
  }
  if(dep.has(activeEffect)) return;
  dep.add(activeEffect);
  activeEffect.deps.push(dep);
}
//新增
function isTracking () {
  return shouldTrack && activeEffect!== undefined;
}
export function trigger(target,key){
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);
  for(const effect of dep){
    if(effect.scheduler){
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

export function effect(fn, options: any= {}){
  const _effect = new ReactiveEffect(fn,options.scheduler);
  // options
  Object.assign(_effect,options);
  // extend
  extend(_effect,options);
  _effect.onStop = options.onStop;
  _effect.run();
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner
}

export function stop(runner){
  runner.effect.stop()
}

三、执行测试

完成后执行单元测试,发现对于obj.prop++的测试可以正常通过了。

yarn test effect