Vue3源码阅读笔记【reactive和effect的理解和实现】

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: anyconstructor(fn: Function) {this._fn = fn}run(){activeEffect = thisthis._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() // 用于存储所有的目标对象(即响应式对象)以及它们对应的 depsMap
export 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] 相关的 effectconst 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)}// 执行所有相关的 effecteffects.forEach((effect) => {effect()})
}

关于响应式依赖的思考

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

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

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

总结

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

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-BlogPHP 1.7.3

Copyright www.122dh.com Rights Reserved.