• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Vue3源码实现 effect 的 stop 功能

武飞扬头像
Aybuaiy吖
帮助39

实现 effect 的 stop 功能

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;
}

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,感谢

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanfekje
系列文章
更多 icon
同类精品
更多 icon
继续加载