likes
comments
collection
share

用手写来理解Vue3响应式原理

作者站长头像
站长
· 阅读数 23

好好学习,天天向上! 感谢各位三连

前言

vue响应式原理...这些类似的话语,想必各位前端朋友都不会陌生吧,如同大家一样,我也会经常听到类似的话,但是即便如此,也只是停留在语言层面,业务流程层面,要想彻底搞懂,也还是差点意思。今天就尝试用代码来实现一下,帮助理解响应式原理(Vue3)。

响应式的业务逻辑

创建一个响应式对象 reactive({count: 0})

// 这是最后我们需要实现的

const state = reactive({ count: 0 });
let newAge;
effect(() => {
      newAge = state.count + 1;
});

可以看到 vue3中暴露出来的reactive API就是创建响应式的关键,所以我们要实现这个函数,在实现之前,我们还是要先搞清楚,它在内部做了什么。

  1. 首先,内部会创建一个 Proxy 对象,重写这个对象的 setget函数,并返回这个对象。
  2. 重写setget,就是为了在这两个阶段,添加自己的逻辑,也就是配合常说的发布订阅模式,在get时,收集依赖,set时,触发依赖。
  3. 要想响应式,还需要借助一个函数effect,该函数的参数也就是当前响应式对象的依赖函数。
  4. 收集依赖(track)和触发依赖(trigger)。
  5. 依赖的收集在一个容器(deps)里面,每次执行,将取出对应依赖并执行。

代码实现

reactive

作用:将传入的对象变为响应式

export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key);
      // 依赖收集
      track(target, key);
      return res;
    },
    set(target, key, value) {
      const res: boolean = Reflect.set(target, key, value);
      // 触发依赖
      trigger(target, key);
      return res;
    },
  });
}

effect

作用:初始化执行依赖,对依赖函数进行包装,让每次依赖执行的时候,都是调用 run

// 用来包装依赖函数的类
class ReactiveEffect {
  private _fn;
  constructor(fn) {
    this._fn = fn;
  }
  run() {
    activeEffect = this;
    this._fn();
  }
}

// 存出 effect 实例
let activeEffect;
export function effect(effectFn) {
  // 包装 effectFn
  const _effect = new ReactiveEffect(effectFn);
  // 第一次执行
  _effect.run();
}

track & trigger

作用:track:在get时收集依赖,trigger:在set时触发依赖。

track:

  1. 定义一个 targetMap存储对象对应的depsMap
  2. 定义一个 depsMap存储对应key的依赖函数(effect
  3. 将对应依赖函数(activeEffect)以set数据格式存储在depsMap

triiger:

  1. 在对应targetMap中取出depsMap
  2. 在对应depsMap中取出dep
  3. dep内的所有effectFn执行
const targetMap = new Map();
export function track(target, key) {
  // {target: { key: [ effectFn ]}}
  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);
}

export function trigger(target, key) {
  // 触发依赖函数
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);

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

测试结果

describe("effect", () => {
  it("happy path", () => {
    // initialization
    const user = reactive({ age: 22 });
    let newAge;
    // 依赖函数,在get时,收集,set时触发
    effect(() => {
      newAge = user.age + 1;
    });
    expect(newAge).toBe(23);
    // update
    user.age++;
    expect(newAge).toBe(24);
  });
});

总结

  1. Vue3 使用了Proxy对象做劫持,重写getset
  2. get时,存储依赖函数,set时,触发依赖函数
  3. 依赖存储是,以target对象,存储每个depsMap(以key值存储每个[...effectFn])

这就是以上全部内容,希望能帮助到你更好理解Vue3响应式原理

参考

以上代码,参考了大佬的开源项目: mini-vue

转载自:https://juejin.cn/post/7200716788869791799
评论
请登录