Vue3 中实现一个最简单的响应式

55 阅读2分钟
// 存储当前正在执行的副作用函数
let activeEffect;
// 保存依赖的地方
const bucket = new WeakMap();
// 副作用函数是可以嵌套执行的,所以我们需要一个 stack 来保存嵌套的副作用函数,方便执行完副作用函数之后正确的回退到外层副作用函数
const effectStack = [];

// 追踪依赖
function track(target, key) {
  if (!activeEffect) return;
  let depsMap = bucket.get(target);
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()));
  }
  let deps = depsMap.get(key);
  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }
  deps.add(activeEffect);
  activeEffect.deps.push(deps);
}
// 触发依赖
function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  const effectsToRun = new Set();
  effects &&
    effects.forEach((effectFn) => {
      // 解决无限递归的问题
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn);
      }
    });
  effectsToRun.forEach((fn) => fn());
}
//
function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i];
    deps.delete(effectFn);
  }
  effectFn.deps.length = 0;
}
// 副作用函数
function effect(fn) {
  const effectFn = () => {
    // 在执行 effectFn 函数之前,需要清空遗留的副作用函数
    // 比如在第一次执行副作用函数的时候使用到了 a 变量,第二次执行副作用函数的时候没有使用到 a 变量,如果我们不及时清除副作用函数
    // 即使我们没有使用 a 变量了,a 变量的改变也会导致副作用函数的重新执行。
    cleanup(effectFn);
    activeEffect = effectFn;
    // 执行辅作用函数之前,将当前副作用函数入栈
    effectStack.push(activeEffect);
    fn();
    // 副作用函数执行完毕后,出栈
    effectStack.pop();
    // 将当前副作用函数设置为栈顶元素,也就是外层副作用函数
    activeEffect = effectStack[effectStack.length - 1];
  };
  // 用来保存当前副作用函数被哪些依赖收集了
  effectFn.deps = [];
  effectFn();
}
// 将一个对象转换为响应式
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      // 收集依赖
      track(target, key);
      return result;
    },
    set(target, key, newVal, receiver) {
      const result = Reflect.set(target, key, newVal, receiver);
      // 触发依赖
      trigger(target, key);
      return result;
    },
  });
}

const obj = {
  count: 1,
};

const data = reactive(obj);

function render() {
  document.body.innerText = data.count;
}

effect(render);

setTimeout(() => {
  data.count++;
}, 1000);

该文章仅作为个人学习记录,若对你有帮助,还请点赞支持;若你觉得对你无用,请无视,勿喷!