精读《Vuejs设计与实现》(15)之响应系统5

139 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情

4.3 设计一个完善的响应式系统

上文有一点忘写了,就是对代码的封装。我们在proxy的get,set方法中实现了数据的响应,这时候,我们最好对代码进行一定封装,这样可以更方便后续的使用和迭代。

const effectStore = new WeakMap()
let activeEffect
//在get时调用
const track = (target,key)=>{
    if(!activeEffect){retruen}
    let deps = effectStore.get(key)
    if(!deps){
        deps.set(key,(deps = new Set()))
    }
    deps.add(activeEffect)
}
//在set时调用
const trigger  = (target,key)=>{
 const deps = effectStore.get(key)
 deps && deps.forEach(fn=>fn())
}

这时候我们发现,我们的proxy永远只能对应一个target对象,但是99.99%的时候,我们需要同时响应多个对象,那么如何实现呢?

image.png

其实就像effectStore一样,我们再次建立一个target的对应关系,这时候可不能用WeakMap了。 在这个对应关系中,key应该是target,也就是原始数据,而值则是我们被代理的对象,也就是我们的数据。

因此,我们这样修改代码,再给他添加一重依赖。

const depsMap = new WeakMap()
const targetMap = new Map()
let activeEffect
//在get时调用
const track = (target,key)=>{
    if(!activeEffect){retruen}
      let depsMap = targetsMap.get(target);
      if (!depsMap) {
        targetsMap.set(target, (depsMap = new Map()));
      }
      let dep = depsMap.get(key);
      if (!dep) {
        depsMap.set(key, (dep = new Set()));
      }
      dep.add(activeEffect);
}
//在set时调用
const trigger  = (target,key)=>{
 let depsMap = targetsMap.get(target);
  if (!depsMap) {
    return;
  }
  let deps = depsMap.get(key);
 deps && deps.forEach(fn=>fn())
}

其实这里逻辑就有点复杂了,很多人到这一步就会被依赖搞混了。 首先,这里有一个Map,这个map储存的是数据之间的对应关系,然后每次先找对应数据,再通过key去找副作用函数,这样就实现了同时响应多个数据。

在之前的代码里,我们总是赋值了一个data,用了一个proxy。但是实际开发中,我们会有很多个data,因此我们需要把他们之际的对应关系储存起来。

到这里,其实我们已经可以实现一个简单的reactive了,他有着和vue/reactive一样的api。

全部代码如下,如有错误,请留言指正

//这里为了方便debbug,把它放到了window下
window.targetsMap = new WeakMap();
let activeEffect = null;
// 执行effect函数,也就是更新数据
const effect = (func) => {
  activeEffect = func;
  func && func();
  activeEffect = null;
};
// 添加依赖追踪
const track = (target, key) => {
  if (!activeEffect) {
    return;
  }
  let depsMap = targetsMap.get(target);
  if (!depsMap) {
    targetsMap.set(target, (depsMap = new Map()));
  }
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  dep.add(activeEffect);
};
// 触发更新
const trigger = (target, key) => {
  let depsMap = targetsMap.get(target);
  if (!depsMap) {
    return;
  }
  let dep = depsMap.get(key);
  if (dep) {
    dep.forEach((fn) => {
      fn();
    });
  }
};
//对于引用类型的响应式
const reactive = (target) => {
  const handler = {
    get(target, key, receiver) {
      console.log('get handler called for', key);
      const result = Reflect.get(target, key, receiver);
      track(target, key);
      return result;
    },
    set(target, key, newValue, receiver) {
      console.log('set handler called for', key, '=', newValue);
      const oldValue = target[key];
      const result = Reflect.set(target, key, newValue, receiver);
      if (result && oldValue !== newValue) {
        trigger(target, key);
      }
      return result;
    },
  };

  return new Proxy(target, handler);
};