likes
comments
collection
share

一起学Vue3源码,实现最简Vue3【05】 - 实现 effect 的 stop 功能

作者站长头像
站长
· 阅读数 37

实现 effect 的 stop 功能

本章继续来完善effect 功能。 什么是stop,简而言之就是拿到调用 effect 返回的runner,把当前返回runner的effect 实例从 依赖收集中清空掉。还是老样子,测试驱动开发。

effect.spec.ts

 it("stop", () => {
    let dummy;
    const obj = reactive({ prop: 1 });
    const runner = effect(() => {
      dummy = obj.prop;
    });
    // 只触发了 set 操作,即触发依赖
    obj.prop = 2;
    expect(dummy).toBe(2);

    stop(runner);
    // get => set
    obj.prop++;
    expect(dummy).toBe(2);

    runner();
    expect(dummy).toBe(3);
  });

从测试来看,会拿到当前的effect 返回的runner作为参数传值,但是runner 下的effect实例从哪里获取呢? 上一讲中,调用effect 时,会把runner 返回,在返回runner时候把当前effect实例挂载到 runner上,就可以拿到了。

effect.ts

// 执行函数
function effect(fn, options: any = {}) {
  const _effect = new ReactiveEffect(fn, options?.scheduler);
  // options 继承给 _effect,其中就包括了onStop fn
  extend(_effect, options);

  _effect.run();
  const runner: any = _effect.run.bind(_effect);

  // 把 effect 挂载到runner上
  runner.effect = _effect;

  return runner;
}


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

下面来实现stop()

// 全局 stop 状态, true 没触发  false 为触发
let shouldTrack: Boolean;

export class ReactiveEffect {
  private _fn: Function;
  // 用来收集 effect 上所有dep 的数组
  deps: Array<ReactiveEffect> = [];
  active: Boolean = true;
  public scheduler: Function | undefined;

  // 公共属性 scheduler 才会被允许在类外部执行
  constructor(fn, scheduler?: Function) {
    this._fn = fn;
    this.scheduler = scheduler;
  }

  run() {
  	// 如果调用了 stop,就直接调用 函数
    if (!this.active) {
      return this._fn();
    }

    // 应该收集依赖
    shouldTrack = true;
    activeEffect = this;

    const res = this._fn();

    // reset
    shouldTrack = false;

    return res;
  }

  stop() {
    // 外部不管调用n次,节约性能调用一次即可
    if (this.active) {
      // 语义化抽离出功能, 清除依赖中的effect
      cleanupEffect(this);
      this.active = false;
    }
  }
}

function cleanupEffect(effect) {
  // 从effect上的依赖中删除当前的 effect
  effect.deps.forEach((dep: any) => {
    dep.delete(effect);
  });
  // 清空空 Set,节约空间
  effect.deps.length = 0;
}

那么effect上的依赖deps 又是怎么来的呢? 答案是收集依赖的时候最后在把依赖全部push到deps数组中

// 收集依赖
function track(target, key) {
  if (!isTracking()) return;
  // target => key => dep
  // target 目标对象
  // key 目标对象中的属性
  // dep 属性关联的函数(不可以重复)
  let depsMap = targetsMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetsMap.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);
  // 把依赖全部push到effect.deps上
  activeEffect.deps.push(dep);
}

export function isTracking() {
  // 单纯的走reactive/ref测试,执行fn,后会触发get操作,即收集依赖函数会被调用,而此时,没有执行effect,所以是没有effect 实例的, 即 activeEffect = undefined
  return shouldTrack && activeEffect !== undefined;
}

一起学Vue3源码,实现最简Vue3【05】 - 实现 effect 的 stop 功能

ok, 测试通过,下面再实现一个简单的 onStop 即,在ReactiveEffect 类上 加一个onStop 属性,只要调用了stop他就会执行一次

effect.spec.ts

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

    stop(runner);
    expect(onStop).toHaveBeenCalledTimes(1);
  });

effect.ts

export class ReactiveEffect {
  private _fn: Function;
  deps: Array<ReactiveEffect> = [];
  active: Boolean = true;
  public scheduler: Function | undefined;
  // 不是必传属性
  onStop?: () => void;
  ......
  stop() {
    // 外部不管调用n次,节约性能调用一次即可
    if (this.active) {
      // 语义化抽离出功能, 清除依赖中的effect
      cleanupEffect(this);
      // 如果有onStop 就执行
      if (this.onStop) this.onStop();
      this.active = false;
    }
  }
}

以上,执行全部测试,全部通过。

总结

本章实现stop方法难点在于,什么时候该收集依赖,什么时候不应该收集依赖。 全局设置shouldTrack属性,用来监控是否应该收集依赖的; 调用 effect 中fn(也就是effect中的 run )就会 触发收集依赖操作,初始化的时候,先把shouldTrack置为 true,然后执行fn,fn中先触发收集依赖,然后赋值给 dummy,然后把fn作为返回值返回给runner,之后把全局属性shouldTrack 置为false。 obj.prop = 2,会重新执行fn,然后回重新给dummy 赋值,赋值过程中会重新收集依赖把 当前2收起来 后面执行 stop 会把当前effect实例从dep中删除,且停止依赖收集,后续dummy就还是2,直到重新执行runner,才被返回当前实际的值。

最后,希望能和每一位读者一起讨论问题,共同进步,详细代码全在这里 => git地址:mini_vue 如果对你有帮助的话,麻烦帮忙点下git star,感谢

转载自:https://juejin.cn/post/7206958960771432504
评论
请登录