vue3源码学习(三) effect

57 阅读1分钟

effect

  • 其实想要渲染的话就只要执行 effect 中的回调函数就行了.
  • 要是那么简单就好了
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>vue3</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module">
      import { reactive, effect } from "./reactivity.js";
      const data = { name: "aa", age: "bb" };
      const state = reactive(data);

      effect(() => {
        app.innerHTML = state.name + state.age;
      });
    </script>
  </body>
</html>

reactive/src/effect.ts

class ReactiveEffect {
  fn;
  constructor(fn) {
    this.fn = fn;
  }
  run() {
    this.fn();
  }
}

export function effect(fn, options) {
  const _effect = new ReactiveEffect(fn);

  _effect.run();
}

然后在 reactive/src/index.ts 中导入

...
export * from "./effect";

依赖收集

reactivity/src/effect.ts

  • 依赖收集的数据结构 Map{ target: Map{ key: Set}}
  • 在每一次收集依赖之前都要清理,例子就是 那种有 v-if 的那种结构,如果不清理,下一次可能还在依赖中
let activeEffect;
const targetMap = new WeakMap();

class ReactiveEffect {
  fn;
  parent;
  deps = [];
  constructor(fn, public scheduler?) {
    this.fn = fn;
  }
  run() {
    // 这里就是考虑effect 套effect的这种情况
    try {
      this.parent = activeEffect;
      // 把当前的ReactiveEffect放到了全局上
      activeEffect = this;
      cleanupEffect(this);
      return this.fn();
    } finally {
      // 这里的finally的执行时机就是fn执行完了
      activeEffect = this.parent;
      this.parent = undefined;
    }
  }
}

// 清理 每次都要重新收集
function cleanupEffect(effect) {
  const { deps } = effect;
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect);
    }
  }
  deps.length = 0;
}

// 结构就是这样
// {name: 'aa', age: 20}: Map({ name: Set()})
export function track(target, key) {
  if (activeEffect) {
    let depsMap = targetMap.get(target);
    // 第一次进来肯定是没有的
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
      depsMap.set(key, (dep = new Set()));
    }
    trackEffects(dep);
  }
}

function trackEffects(dep) {
  let shouldTrack = false;

  // 源码中这里用了很多的位运算 暂时不考虑
  shouldTrack = !dep.has(activeEffect!);

  if (shouldTrack) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
  }
}

export function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    // 没有被追踪过
    return;
  }
  let effects = depsMap.get(key);
  triggerEffects(effects);
}

// 其实就是拿所有的dep执行
export function triggerEffects(dep) {
  const effects = [...dep];
  for (const effect of effects) {
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

export function effect(fn, options?) {
  const _effect = new ReactiveEffect(fn, options.scheduler);

  if (!options || !options.lazy) {
    _effect.run();
  }

  const runner = _effect.run.bind(_effect);

  return runner;
}

reactivity/src/baseHandlers.ts

import { track, trigger } from "./effect";

export const MutableReactiveHandler = {
  get(target, key, receiver) {
    ...
    track(target, key)
    ...
  },
  set(target, key, value, receiver) {
    let oldValue = target[key]
    const result = Reflect.set(target, key, value, receiver);
    if (hasChanged(value, oldValue)) {
      trigger(target, key)
    }
    return result;
  },
};