模拟computed

93 阅读2分钟

期望效果:Vue的计算属性有个特点是仅当依赖变化时才重新计算,不用担心频繁调用时损耗性能。在Vue项目之外也会有要用计算属性的场景。

  1. 计算属性是个函数,计算结果仅当依赖属性发生变化时才重新计算。
  2. 计算属性需要收集依赖,并在依赖发生变化时触发更新重新求值。
  3. 收集依赖可以利用Proxy或Object.defineProperty的get来劫持调用。
  4. 触发更新可以利用Proxy或Object.defineProperty的set来劫持调用。

先劫持get和set

export const myRef = (value) => {
  const obj = { value };
  const proxy = new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key);
      console.log("get", target, key);
      return res;
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value);
      console.log("set", target, key);
      return res;
    },
  });
  return proxy;
};
  1. 保存计算属性的getter
export let computedCallback: null | (() => any) = null;

export const myComputed = (getter: () => any) => {
  let lastValue;
  const update = () => {
    console.log("更新前", lastValue);
    lastValue = getter();
    console.log("更新后", lastValue);
  };
  computedCallback = update;
  update(); // 这里执行的时候可以收集依赖
  computedCallback = null;
  return {
    get value() {
      return lastValue;
    },
  };
};

编写用来收集依赖和触发依赖的回调函数

interface CallbackMap {
  [key: string]: Array<() => any>;
}
// 追踪
export const tracker = (target, key) => {
  if (computedCallback && typeof key === "string") {
    const callbackMap: CallbackMap = computedMap.get(target) || {};
    callbackMap[key] = callbackMap[key] || [];
    callbackMap[key].push(computedCallback);
    computedMap.set(target, callbackMap);
  }
};

// 触发
export const trigger = (target, key) => {
  const callbackMap: CallbackMap | null = computedMap.get(target);
  if (callbackMap && typeof key === "string") {
    callbackMap[key].forEach((callback) => callback());
  }
};

将收集依赖回调放到Proxy的get里,将触发依赖回调放到Proxy的set里

export const myRef = (value) => {
  const obj = { value };
  const proxy = new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key);
      console.log("get", target, key);
      tracker(target, key); // 收集依赖
      return res;
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value);
      console.log("set", target, key);
      trigger(target, key); // 触发更新
      return res;
    },
  });
  return proxy;
};

当依赖属性不是基本类型时

export const myReactive1 = <T extends object>(obj: T) => {
  const proxy = new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key);
      console.log("get", target, key);
      tracker(target, key);
      return res;
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value);
      console.log("set", target, key);
      trigger(target, key);
      return res;
    },
  });
  return proxy;
};

当依赖属性的层级较深时需要递归返回Proxy

const myReactiveMap = new WeakMap();

export const myReactive2 = (obj: any) => {
  const existProxy = myReactiveMap.get(obj);
  if (existProxy) {
    return existProxy;
  }
  const proxy = new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key);
      console.log("get", target, key);
      tracker(target, key);
      return res instanceof Object ? myReactive2(res) : res;
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value);
      console.log("set", target, key);
      trigger(target, key);
      return res;
    },
  });
  myReactiveMap.set(obj, proxy);
  return proxy;
};