手撸mini-vue之reactive&effect&依赖收集&触发依赖

270 阅读1分钟

vue3的响应式数据核心是reactive,reactive的核心是effect

F0625B05-5CCC-487e-9B5D-FFF5C4465EDC.png

下面来实现上图的流程

实现reactive

// reactive.ts
export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      let res = Reflect.get(target, key);
      
      // 依赖收集
      track(target, key);
      
      return res;
    },
    
    set(target, key, value) {
      let res = Reflect.set(target, key, value);
      
      // 触发依赖
      trigger(target, key);
      
      return res;
    },
  })
}

这样就很简单的实现了一个有get、set操作的reactive函数

通过以下单元测试可以验证上面的代码

// reactive.spec.ts
describe("reactive", () => {
  it("happy path", () => {
    const original = { foo: 1 };
    const observed = reactive(original);

    expect(observed).not.toBe(original);
    expect(observed.foo).toBe(1);
  });
});

实现effect

// effect.ts
class ReactiveEffect {
  private _fn;
  constructor(fn) {
    this._fn = fn;
  }
  
  run() {
    this._fn();
  }
}
export function effect(fn) {
  let _effect = new ReactiveEffect(fn);
  // effect初始直接执行fn
  _effect.run();
}

实现依赖收集

既然要依赖收集,依赖收集起来应该存放在哪里

微信截图_20220224152219.png

// effect.ts
class ReactiveEffect {
  private _fn;
  constructor(fn) {
    this._fn = fn;
  }
  
  run() {
    // 调用run 证明是正在执行的状态
    activeEffect = this;
    this._fn();
  }
}

let targetMap = new Map();
export function track(target, key) {
  let depsMap = targetMap.get(target);
  
  if(!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  
  let dep = depsMap.get(key);
  
  if(!dep) {
    // fn是唯一的 所以这里用Set
    dep = new Set();
    depsMap.set(key, dep);
  }
  // activeEffect 是当前effect实例对象
  dep.add(activeEffect);
}

let activeEffect;
export function effect(fn) {...}

实现触发依赖

// effect.ts
export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);
  
  for(let effect of dep) {
    effect.run();
  }
}

通过以下单元测试来验证

// effect.spec.ts
describe('effect',() => {
  it('happy path', () => {
    let user = reactive({ age: 10 });
    let nextAge;
    
    effect(() => {
      nextAge = user.age + 1;
    });
    
    expect(nextAge).toBe(11);
    
    user.age++;
    expect(nextAge).toBe(12);
  })
})

源码地址戳这里