手写mini版本的Vue3--实现 effect & reactive & 依赖收集 & 触发依赖

343 阅读3分钟

介绍

开头继续感谢崔大的mini-vue 项目。

项目地址:github.com/cuixiaorui/…

强烈建议大家把项目 down 下来,运行一下,看VUE3 的整体运行逻辑。

vue2的响应式的逻辑是使用Object.defineProperty 这个函数来进行监听。vue3则是使用了proxy 这个更强大的代理器,提供了多达十三种监听方法。具体可以去看阮一峰的教程

今天我们就来实现 effect 和reactive 并且我们实现依赖收集还有触发依赖。

实现reactive

我们先创建一个reactive 文件,然后在文件里面导出一个reactive 方法。

首先是我们实现一下获取值的方法,传入的是一个目标对象 target,还有对象的 key。

我们可以使用Reflect.get的方式,得到对应的值,然后直接 return 出去。

设置值的也是类似,传入的是目标 target,想要修改的 key,还有对应的值。

我们也是使用Reflect.set 的方式来设置。

export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key)
         // TODO:收集依赖
      return res
    },

    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      // TODO:触发依赖
      return res
    },
  })
}


这样子就完成了一个基本上的响应式数据。可以获取值,设置值。

接下来就是在获取值的时候实现我们的依赖收集,在设置值的时候去触发依赖。

依赖收集

依赖收集的核心逻辑就是把目标对象的 key 给收集起来,然后放到一个容器里面。

关系链条就是,我们的 target 目标对象对应 key,key 对应 dep。

首先声明一个targetMap,这个用来存放 target 的。

然后我们使用 depsMap 在targetMap里面取出值。

如果depsMap 不存在的话,我们先它存起来。

dep 也是一样的,如果没有就初始化一下,存储起来。

最后我们需要把用户需要执行的函数-fn 给存到dep 里面

const targetMap = new Map();
export function track(target, key) {
  // target -> key -> dep
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }

  dep.add(activeEffect);
}

实现effect

class ReactiveEffect {
  private _fn: any;

  constructor(fn) {
    this._fn = fn;
  }
  run() {
    activeEffect = this;
    this._fn();
  }
}

let activeEffect;
export function effect(fn) {
  // fn
  const _effect = new ReactiveEffect(fn);

  _effect.run();
}

effect最重要就是保存用户传进来的 fn。

在这里使用的是一个类的实现,把逻辑再抽离一层。

然后实例化这个类,执行类的函数run。run 里面把全局变量 activeEffect 赋值等于用户传入的函数。并且执行用户传入进来的 fn。

触发依赖

export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);

  for (const effect of dep) {
    effect.run();
  }
}

触发依赖,主要是根据 target 和key 取出收集到的 fn,然后执行。

首先是用 target 从targetMap 取出我们的 depsMap

然后再用 key 从targetMap取出 dep。

然后使用 for 循环,执行 dep 里面的 effect,然后执行里面的 run 方法。

结尾

项目已经放到我的 GitHub 上面了,欢迎大家去start。

我的项目地址:github.com/moyuhaokan/…

再次推荐崔大的项目:github.com/cuixiaorui/…