目标

  1. 为 effect 提供一个 stop 方法
  2. 当使用 stop 传入 runner 调用时会停止调用 effect
  3. 当手动调用 runner 时,又会调用 effect ```typescript import { effect, stop } from ‘../effect’;

it(‘stop’, () => { let dummy; const obj = reactive({ foo: 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); });

  1. <a name="bk7Ag"></a>
  2. # 实现
  3. ```typescript
  4. class EffectReactive {
  5. private _fn;
  6. // 增加一个属性存储依赖
  7. deps = [];
  8. // 是否激活状态
  9. active = true;
  10. constructor(fn, public scheduler?) {
  11. this._fn = fn;
  12. }
  13. run() {
  14. activeEffect = this;
  15. return this._fn();
  16. }
  17. stop() {
  18. // 如果已经清空过(失活了),即使外部多次调用也不会清空
  19. if(this.active) {
  20. // 清空依赖
  21. cleanupEffect(effect);
  22. this.active = false;
  23. }
  24. }
  25. }
  26. function cleanupEffect(effect) {
  27. effect.deps.forEach((dep: any) => {
  28. dep.delete(effect);
  29. });
  30. }
  31. let activeEffect;
  32. export function track(target, key) {
  33. let depsMap = targetMap.get(target);
  34. if (!depsMap) {
  35. depsMap = new Map();
  36. targetMap.set(target, depsMap);
  37. }
  38. let dep = depsMap.get(key);
  39. if (!dep) {
  40. dep = new Set();
  41. depsMap.set(key, dep);
  42. }
  43. // 重新运行所有单测时,会发现 effect 的 happy path 不通过
  44. // activeEffect 有可能是 undefined,会导致 activeEffect.deps 报错
  45. if (!activeEffect) return;
  46. dep.add(activeEffect);
  47. // 为当 track 时反向存储一下对应的依赖,以便 stop 时作清空
  48. activeEffect.deps.push(dep);
  49. }
  50. export function effect(fn, options: any = {}) {
  51. const _effect = new EffectReactive(fn, options.scheduler);
  52. _effect.run();
  53. const runner: any = _effect.run.bind(_effect);
  54. // 在 runner 挂载当前的 effect
  55. runner.effect = _effect;
  56. return runner;
  57. }
  58. export function stop(runner) {
  59. // 指向 EffectReactive 的 stop 方法
  60. runner.effect.stop();
  61. }

onStop 目标

  1. 通过 effect 第二个 options 参数的属性传入 onStop 函数
  2. 当调用 stop 之后会被调用 onStop 回调函数

    1. it('onStop', () => {
    2. const obj = reactive({ foo: 1 });
    3. const onStop = jest.fn();
    4. let dummy;
    5. const runner = effect(
    6. () => {
    7. dummy = obj.foo;
    8. },
    9. { onStop }
    10. );
    11. stop(runner);
    12. expect(onStop).toBeCalledTimes(1);
    13. });

    实现 onStop

    ```typescript class EffectReactive { private _fn; deps = []; active = true;

    // 声明一个 onStop onStop?: () => void;

    constructor(fn, public scheduler?) { this._fn = fn; } run() { activeEffect = this; return this._fn(); } stop() { if(this.active) { cleanupEffect(effect); // 清除后,如果存在 onStop 就调用 if(this.onStop) {

    1. this.onStop();

    } this.active = false; } } }

export function effect(fn, options: any = {}) { const _effect = new EffectReactive(fn, options.scheduler); _effect.onStop = options.onStop;

_effect.run();

const runner: any = _effect.run.bind(_effect); // 在 runner 挂载当前的 effect runner.effect = _effect;

return runner; }

  1. <a name="aLQZf"></a>
  2. # 重构优化
  3. ```typescript
  4. import { extend } from '../shared';
  5. export function effect(fn, options: any = {}) {
  6. const _effect = new EffectReactive(fn, options.scheduler);
  7. // 因为后面可能存在更多的 options, 可通过 Object.assign 进行挂载
  8. // _effect.onStop = options.onStop;
  9. // Object.assign(_effect, options);
  10. // // 可以为 Object.assign 重构得更有语义化,在 shared 公共工具函数定义 extend 方法
  11. extend(_effect, options);
  12. _effect.run();
  13. const runner: any = _effect.run.bind(_effect);
  14. runner.effect = _effect;
  15. return runner;
  16. }
  1. export const extend = Object.assign;