vue-next-reactivity debug + 乞丐版实现

1,059 阅读3分钟

这边文章主要面向准备着手看vue-next源码,但是无从下手的朋友

  1. 如何通过chrome + vscode debug Vue3 的测试案例
  2. reactivity 精简版(只包含了最基本的数据流逻辑)
  3. 非复刻源码,只注重怎么实现dataFlow

通过chrome + vscode debug Vue3源码

【博客地址】: shancw96.github.io/blogs/2020/…

开启chrome 调试界面 chrome://inspect

  1. 打开 chrome,并输入地址: chrome://inspect
  2. 选择 Open dedicated DevTools for Node

在 package.json - script 添加如下命令

...
"scripts": {
	...
    "test:debug": "node --inspect node_modules/.bin/jest --runInBand"
  },
...

开始对测试案例进行debug

npm run test:debug <path_to_test>

example:debug reactivity 模块下的reactive.spec.ts

相对路径(vue-next 根目录):./packages/reactivity/__tests__/reactive.spec.ts

  1. 开启chrome://inspect设置

  2. 在测试文件或者源文件(独立模块)中添加debugger 测试模块中添加 或者比如独立模块effect

  3. 执行debug 命令 对某一个测试文件进行debug

    npm run test:debug ./packages/reactivity/__tests__/reactive.spec.ts

  4. 自动触发debug

Vue3 响应式流程

  1. 将待响应式的对象传入 reactive 函数,进行 proxy 代理,主要为 get 和 set

  2. 为 effect 函数传入对应的回调方法,effect 函数会在初始化的时候执行一次,触发 proxy 的 get 操作,触发 track 操作,对当前依赖进行收集

  3. 对响应式数据进行 set 操作触发 trigger 操作,执行对应的 effect 列表

数据结构:

代码实现

reactive 功能

reactive 主要是代理对象,实现响应式 reactive: 使用 proxy 代理传入的对象

function reactive(target) {
  // 1. 判断是否为对象,proxy 只能代理对象
  if (!isObject(target)) return target;
  const proxy = new Proxy(target, {
    get,
    set,
  });
  return proxy;
}

get 操作:主要为依赖收集,触发 track 功能

let targetMap = new WeakMap();
function get(target, key, receiver) {
  const res = Reflect.get(target, key, receiver);
  // 依赖收集
  track(target, key);
  // proxy 只代理了当前层的key,如果key 对应的value 是嵌套的,那么需要进行对应的依赖收集
  if (isObject(res)) {
    reactive(res);
  }
  return res;
}
function track(object, key) {
  // 如果没有d副作用函数,则不进行依赖收集
  if (!activeEffect) {
    return;
  }
  // 有副作用函数需要将副作用函数和当前对象的具体key相互关联
  // 1.按照对象初始化分组
  let depsMap = targetMap.get(object);
  if (!depsMap) {
    targetMap.set(object, (depsMap = new Map()));
  }
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  //2. key <-> Array<Effect>
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
  }
}

set 操作:触发副作用函数列表

function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  trigger(target, key);
  return result;
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap.get(key)) {
    // 从没有被track
    return;
  }
  const effects = new Set();
  // 将所有的非 activeEffect 加入到等待执行队列中
  const add = (effectsToAdd) => {
    if (!effectsToAdd) return;
    effectsToAdd.forEach((effect) => {
      if (effect !== activeEffect) {
        // question 为什么不能是activeEffect. ans: 参考例1情况,如果不排除activeEffect,就会循环执行trigger
        effects.add(effect);
      }
    });
  };

  // 将depsMap中 key 对应的effect 复制一份,放到等待执行队列中
  if (key !== void 0) {
    add(depsMap.get(key));
  }
  //执行队列
  effects.forEach((effect) => effect());
}

effect 功能

effect 是副作用函数,与 vue2 的 Watcher 功能相同,主要为双向绑定触发后执行的函数

activeEffect = undefined;
function effect(fn) {
  const effect = createReactiveEffect(fn);
  effect();
  return effect;
  function createReactiveEffect(fn) {
    const effect = function reactiveEffect() {
      try {
        activeEffect = effect;
        return fn();
      } finally {
        activeEffect = undefined;
      }
    };
    effect.deps = [];
    return effect;
  }
}

完整代码:✈️ 传送门

写在最后:上述代码根据vue3源码进行大量删减,保留最基本的逻辑功能(依赖收集),会存在很多问题,只进行了基本类型的测试,勿杠。