Vue3源码中的reactive和effect的理解和实现

在Vue3中,reactive和effect是两个非常重要的API,用于实现响应式数据和副作用函数。本文将介绍它们的基本用法,以及简单的实现原理。

文章目录

  • Vue3源码中的reactive和effect的理解和实现
    • 深入reactive
    • 深入effect
    • 依赖收集track和依赖触发trigger
      • track的简单实现
      • trigger的简单实现
    • 关于响应式依赖的思考
    • 总结

由于源码中逻辑分支较多,代码示例大多都是采用简单实现,帮助理解

深入reactive

reactive是一个工厂函数,用于创建响应式对象。使用reactive函数创建的对象,其属性可以被自动追踪,当属性发生变化时,会自动更新相关的视图。

下面是reactive函数的简单实现:

function reactive(obj) {  return new Proxy(obj, {  get(target, key) {    track(target, key);  // 追踪属性访问    return target[key];  },  set(target, key, value) {    target[key] = value;    trigger(target, key);  // 触发更新  },  };);}

上述代码中,使用Proxy对象实现了对目标对象的代理。当访问代理对象的属性时,会自动调用get方法;当设置代理对象的属性时,会自动调用set方法。

在get方法中,我们调用了track函数,用于追踪属性的访问。track函数的实现可以参考Vue3源码中的effect.ts文件,不在本文讨论范围内。

在set方法中,我们先更新了目标对象的属性值,然后调用了trigger函数,用于触发更新。trigger函数的实现也可以参考Vue3源码中的effect.ts文件。

深入effect

effect是一个函数,用于创建副作用函数。使用effect函数创建的副作用函数,可以自动追踪其内部响应式数据的变化,当数据变化时,会自动重新执行副作用函数。

下面是effect函数的简单实现:

class reactiveEffect {    private _fn: any    constructor(fn: Function) {        this._fn = fn    }    run(){        activeEffect = this        this._fn()    }}let activeEffect export function effect(fn) {    const _effect = new reactiveEffect(fn)activeEffect = _effect // 在这里我们将当前effect赋值到全局属性,当我们调用传入的副作用函数时,也就会访问响应式数据,这时在get方法中收集当前effect,就完成了effect的依赖收集。    _effect.run()    activeEffect = null;}function effect(fn) {  const runner = () => {    fn();  };  runner();    return runner;}

上述代码中,我们创建了一个名为runner的函数,用于执行副作用函数。在创建runner函数后,我们立即执行了一次副作用函数,用于初始化副作用函数的状态。

在副作用函数中访问的响应式数据,会被track函数自动追踪。当响应式数据发生变化时,trigger函数会自动调用runner函数,重新执行副作用函数。

依赖收集track和依赖触发trigger

track的简单实现

const targetMap = new WeakMap() // 用于存储所有的目标对象(即响应式对象)以及它们对应的 depsMapexport function track(target, key) {    let depsMap = targetMap.get(target) //用于存储目标对象的所有响应式属性以及它们对应的依赖列表 dep。    if(!depsMap) {        depsMap = new Map()        targetMap.set(target, depsMap)    }    let dep = depsMap.get(key)// dep 是一个 Set 对象,用于存储所有依赖于某个响应式属性的 effect 函数    if(!dep) {        dep = new Set()    }    dep.push(activeEffect.run) // 将当前effect收集到dep中}

trigger的简单实现

function trigger(target, key = null) {  const depsMap = targetMap.get(target)  if (!depsMap) {    return  }  const effects = new Set()  // 收集所有与 target[key] 相关的 effect  const addEffects = (dep) => {    dep.forEach((effect) => {      effects.add(effect)    })  }  if (key !== null) {    const dep = depsMap.get(key)    if (dep) {      addEffects(dep)    }  } else {  // 如果不指定 key,则会遍历 depsMap 中所有的 dep,收集它们中的 effect 并执行。    depsMap.forEach(addEffects)  }  // 执行所有相关的 effect  effects.forEach((effect) => {    effect()  })}

关于响应式依赖的思考

从以上可以看出,当我们显式调用effect时,很容易就能将副作用函数收集为当前依赖,而在模版内的响应式数据应该如何收集依赖呢

简单来说,Vue 的模板编译器会将模板解析成抽象语法树(AST),然后生成渲染函数。在生成渲染函数时,模板中的数据绑定和指令会被编译成 JavaScript 代码,并将其包装在 render 函数中。render 函数是一个纯函数,它接收一个上下文对象作为参数,返回一个 VNode 对象。在执行 render 函数期间,访问响应式数据的属性会自动触发依赖追踪,将当前正在执行的 render 函数添加到该属性对应的 dep 中。

因此,虽然在 Vue 模板中的响应式数据没有明显的 effect 函数,但是它们的响应式更新机制和通过 effect 函数实现的响应式数据是类似的,都是通过依赖追踪和响应式更新实现的。

总结

reactive和effect是Vue3中实现响应式数据和副作用函数的核心API,学习并理解这部分内容对于深入Vue核心逻辑是很有帮助