Last updated on

Vue3 实现

template

image

编译时

image

运行时

image

image

image

import vue from "rollup-plugin-vue";
import styles from "rollup-plugin-styles";

export default {
  plugins: [
    vue({
      preprocessStyles: true,
    }),
    styles(
      {
        mode: "extract",
        // ... or with relative to output dir/output file's basedir (but not outside of it)
        mode: ["extract", "awesome-bundle.css"],
      }
    ),
  ],
  input: "index.vue",
  output: {
    format: "esm",
    file: "dist/index.js",
  },
};

reactivity

it("Proxy & Reflect ", () => {
  const target = {
    message1: "hello",
    message2: "everyone",
    get message3() {
      return this.message1 + this.message2;
    },
    set xx(x) {
      this.message1 = x;
    },
  };

  const handler = {
    get(target, key, receiver) {
      console.log("read", key, target === receiver);
      // Reflect 处理调用对象的基本方法
      return Reflect.get(target, key, receiver);
    },
    set(target, property, value, receiver) {
      return Reflect.set(target, property, value, receiver);
    },
  };

  const proxy = new Proxy(target, handler);

  proxy.message3;
  proxy.message1 = "2";
  expect(target.message1).toBe("2");
});

https://vitest.dev/guide/debugging

{
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Current Test File",
      "autoAttachChildProcesses": true,
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"],
      "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
      "args": ["run", "${relativeFile}"],
      "smartStep": true,
      "console": "integratedTerminal"
    }
  ]
}
it("should observe basic properties", () => {
  let dummy;
  // init start
  const counter = reactive({ num: 0 });
  effect(() => (dummy = counter.num));
  // init end

  expect(dummy).toBe(0);
  counter.num = 7;
  expect(dummy).toBe(7);
});

初始化

effect(() => (dummy = counter.num)); -> const _effect = new ReactiveEffect(fn); -> _effect.run(); -> activeEffect = this as any; // # 很重要!! -> const result = this.fn(); -> () => (dummy = counter.num) -> counter.num -> return function get(target, key, receiver) { -> track(target, "get", key); -> trackEffects(dep); -> dep.add(activeEffect); (activeEffect as any).deps.push(dep);

初始化的目的是形成 targetMap 全局的 WeekMap

Map<Target extends object, Map<string | symbol, Set<ReactiveEffect>>>

{
    [{num: 0}]: {
        "num": [[new ReactiveEffect(() => (dummy = counter.num))]]
    }
}
export function effect(fn, options = {}) {
  const _effect = new ReactiveEffect(fn);

  // 把用户传过来的值合并到 _effect 对象上去
  // 缺点就是不是显式的,看代码的时候并不知道有什么值
  extend(_effect, options);
  _effect.run();

  // 把 _effect.run 这个方法返回
  // 让用户可以自行选择调用的时机(调用 fn)
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner;
}


export class ReactiveEffect {
  active = true;
  deps = [];
  public onStop?: () => void;
  constructor(public fn, public scheduler?) {
    console.log("创建 ReactiveEffect 对象");
  }

  run() {
    console.log("run");
    // 运行 run 的时候,可以控制 要不要执行后续收集依赖的一步
    // 目前来看的话,只要执行了 fn 那么就默认执行了收集依赖
    // 这里就需要控制了

    // 是不是收集依赖的变量

    // 执行 fn  但是不收集依赖
    if (!this.active) {
      return this.fn();
    }

    // 执行 fn  收集依赖
    // 可以开始收集依赖了
    shouldTrack = true;

    // 执行的时候给全局的 activeEffect 赋值
    // 利用全局属性来获取当前的 effect
    activeEffect = this as any; // # 很重要!!
    // 执行用户传入的 fn
    console.log("执行用户传入的 fn");
    const result = this.fn();

触发变化

counter.num = 7; -> return function set(target, key, value, receiver) { -> trigger(target, "set", key); -> triggerEffects(createDep(effects)); -> effect.run(); -> const result = this.fn();