7-优化 shop 功能

96 阅读2分钟

回顾之前的用例

effect.spec.ts

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

    // 使用了stop,删除了当前依赖
    stop(runner);

    obj.prop = 3;
    expect(dummy).toBe(2);

    // stopped effect should still be manually callable
    runner();
    expect(dummy).toBe(3);
  });

如果把 obj.prop = 3 改成 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,删除了当前依赖
    stop(runner);

    obj.prop++;
    expect(dummy).toBe(2);

    // stopped effect should still be manually callable
    runner();
    expect(dummy).toBe(3);
  });

image.png

报错了? 为什么?思考一下这个问题

  1. obj.prop = 3 只触发了set操作,我们在前面已经用stop去清除对应的effect,所以不会有变化。
  2. obj.prop++ 等于 obj.prop = obj.prop + 1 ,既触发了get,又触发了set,触发get又会把依赖收集,set的时候又会触发。

既然找到了原因,那着手解决! effect.ts

    /** 看得见的思考
     * 1. 在track的时候加个判断标识,如果判断到该标识为true,就不进行收集
     * 2. 调用实例的run时,会track,我们在run里给判断标识赋值
     */
 class Effect {
  private _fn: any;
  scheduler?: Function | undefined;
  stopFlag = false;
  onStop?: () => void;
  // 收集所有的dep
  depMap = [];

  constructor(fn) {
    this._fn = fn;
  }

  // 执行effect接收的fn
  run() {
    // 如果执行了stop,不会继续进行收集。
+    if (this.stopFlag) {
+      return this._fn();
+    }
+    // 保存当前实例,给track收集
+    activeEffect = this;
+    // 初始化收集状态
+    shouldTrack = true;
+    const result = this._fn();
+    /** 看得见的思考
+     * 1. fn调用(track)后,重置收集状态
+     * 2. 避免下一轮fn(track的时候),如果shouldTrack为true,还会被收集进去
+     * 3. track内部判断了shouldTrack,所以要在track后重置收集状态
+     */

+    shouldTrack = false;
+    return result;
  }

  /** 清除当前effect
   * 1. 把所有的dep存起来,再从dep中清除当前的effect
   * 2. 把当前effect从对应的dep中删除,触发依赖的时候就遍历不到该数据
   */
  stop() {
    this.onStop && this.onStop();
    // 避免多次调用
    if (!this.stopFlag) {
      cleanEffect(this);
      this.stopFlag = true;
    }
  }
}

export function track(target, key) {
+  if (!isTracking()) return;
  let depMap = targetMap.get(target);
  if (!depMap) {
    depMap = new Map();
    targetMap.set(target, depMap);
  }
  let dep = depMap.get(key);
  if (!dep) {
    dep = new Set();
    depMap.set(key, dep);
  }
+  // 提取了收集函数
+  trackEffect(dep);
}

+ export function isTracking() {
+   // shouldTrack为true并且当前实例不为undefined,就会进行依赖收集
+   return shouldTrack && activeEffect !== undefined;
+ }

+ export function trackEffect(dep) {
+   // 收集当前不存在的实例
+   !dep.has(activeEffect) && dep.add(activeEffect);
+   // 收集当前的dep
+   activeEffect.depMap.push(dep);
+ }

修改为测试

yarn test effect

image.png