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核心逻辑是很有帮助