4-实现effect的stop && onStop

107 阅读3分钟

stop

effect.spec.ts

/** 实现effect的stop功能
   * 1. 调用stop,要把stop传入的effect清除
   * 2. 调用stop后失去响应
   * 3. 重新调用effect,恢复响应
   */
  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);
  });

effect.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-15 13:11:07
 * @LastEditTime: 2022-03-16 17:01:48
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\src\reactivity\effect.ts
 *
 */

// 当前活跃的effect实例
let activeEffect;
class Effect {
  private _fn: any;
  scheduler: any;
  // 收集所有的dep
+  depMap = [];

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

  // 执行effect接收的fn
  run() {
    activeEffect = this;
    // return 执行结果
    return this._fn();
  }

  /** 清除当前effect
   * 1. 把所有的dep存起来,再从dep中清除当前的effect
   * 2. 把当前effect从对应的dep中删除,触发依赖的时候就遍历不到该数据
   */
+  stop() {
+    this.depMap.forEach((effect: any) => {
+      effect.delete(this);
+    });
+  }
}

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

// 收集依赖
const targetMap = new Map(); // 所有的依赖,触发依赖的时候会从这里面取
export function track(target, key) {
  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);
  }
  // 避免activeEffect为空的时候,取不到depMap
+  if(!activeEffect) return;
  dep.add(activeEffect);
  // 收集当前的dep
+  activeEffect.depMap.push(dep);
}

// 触发依赖
export function trigger(target, key) {
  let depMap = targetMap.get(target);
  let dep = depMap.get(key);
  for (const effect of dep) {
    if (effect.scheduler) {
      effect.scheduler();
      return;
    }
    effect.run();
  }
}

export function effect(fn, option: any = {}) {
  const _effect = new Effect(fn);
  // 实现scheduler
  _effect.scheduler = option.scheduler;
  // 初始化执行
  _effect.run();
  // 实现runner
  const runner: any = _effect.run.bind(_effect);
  // 把当前effect实例加到runner
+  runner.effect = _effect;
  return runner;
}

测试结果

image.png

onStop

effect.spec.ts

/** 实现effect的stop回调通知
   * 调用stop后并且存在onStop,则调用onStop
   */
  it("onStop", () => {
    const onStop = jest.fn();
    const runner = effect(() => {}, {
      onStop,
    });

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

effect.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-15 13:11:07
 * @LastEditTime: 2022-03-16 17:28:07
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\src\reactivity\effect.ts
 *
 */

+ import { extend } from "../shared";

// 当前活跃的effect实例
let activeEffect;
class Effect {
  private _fn: any;
  scheduler?: Function | undefined;
  stopFlag = true;
  onStop?: () => void;
  // 收集所有的dep
  depMap = [];
  constructor(fn) {
    this._fn = fn;
  }

  // 执行effect接收的fn
  run() {
    activeEffect = this;
    // return 执行结果
    return this._fn();
  }

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

+ function cleanEffect(effect) {
+   effect.depMap.forEach((effects: any) => {
+     effects.delete(effect);
+   });
+   effect.stopFlag = false;
+ }

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

// 收集依赖
const targetMap = new Map(); // 所有的依赖,触发依赖的时候会从这里面取
export function track(target, key) {
  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);
  }
  dep.add(activeEffect);
  // 收集当前的dep
  activeEffect.depMap.push(dep);
}

// 触发依赖
export function trigger(target, key) {
  let depMap = targetMap.get(target);
  let dep = depMap.get(key);
  for (const effect of dep) {
    if (effect.scheduler) {
      effect.scheduler();
      return;
    }
    effect.run();
  }
}

export function effect(fn, option: any = {}) {
  const _effect = new Effect(fn);
  // 初始化执行
  _effect.run();
  
  // 添加所有option属性
+  extend(_effect, option);

  // 实现runner
  const runner: any = _effect.run.bind(_effect);
  // 把当前effect实例加到runner
  runner.effect = _effect;
  return runner;
}

新建公用hook

src/shared/index.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-15 19:28:09
 * @LastEditTime: 2022-03-17 16:49:26
 * @LastEditors: Lin zefan
 * @Description: 公用hook
 * @FilePath: \mini-vue3\src\shared\index.ts
 *
 */

export const extend = Object.assign;