9.实现effect的stop功能

144 阅读1分钟

这一小节实现effect的stop功能。

一、单元测试编写

首先,编写单元测试部分代码。主要有2点:

  1. 调用effect的stop,修改响应式对象的值时,effect传入的ffn不再执行
  2. 当effect传入第二个可选参数onStop函数时,调用effect的stop,将执行onStop函数
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;
    expect(dummy).toBe(2);
    // stopped effect should still be manually callable
    runner();
    expect(dummy).toBe(3);
  });

  it("onStop",() => {
    const obj = reactive({
      foo: 1,
    });
    const onStop = jest.fn();
    let dummy;
    const runner = effect(() => {
      dummy = obj.foo;
    }, {
      onStop,
    });
    stop(runner);
    expect(onStop).toBeCalledTimes(1);
  })

二、功能实现

实现要点:

  1. 通过在ReactiveEffect类中添加stop方法,当调用stop方法时,将effect的依赖集合deps都删除
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()
}

image.png

image.png

三、执行单元测试

功能实现过程中,我们可以运行yarn test effect --watch,添加--watch参数,当我们修改代码时,将自动进行测试脚本运行。

yarn test effect --watch

整个过程完成后,我们可以运行yarn test,运行所有的测试,以防止对其他功能的影响。

yarn test