Vue 3 的 Effect(副作用) 是整个响应式系统的核心机制,负责管理依赖追踪和响应式触发。理解其作用和原理对掌握 Vue 的底层机制至关重要。
一、核心作用
1. 依赖追踪(Dependency Tracking)
- 自动跟踪响应式数据在副作用函数中的使用。
- 示例代码:
import { reactive, effect } from 'vue'const obj = reactive({ count: 0 })effect(() => {console.log(`count is: ${obj.count}`) })
- 当首次执行
effect
时,函数() => console.log(...)
会被运行。 - 触发
obj.count
的get
操作,触发依赖收集(将当前effect
关联到obj.count
)。
- 当首次执行
2. 自动响应(Automatic Re-run)
- 当响应式数据的依赖变化时,自动重新执行副作用函数:
obj.count++ // 触发依赖更新,控制台打印 "count is: 1"
3. 支撑高级 API
computed
、watch
、组件渲染函数等底层都依赖于effect
实现。
二、实现原理
1. 核心类:ReactiveEffect
Vue 3 用 ReactiveEffect
类封装副作用逻辑,简化后的源码结构如下:
class ReactiveEffect<T = any> {// 当前 effect 的所有依赖项(其他响应式对象)deps: Dep[] = []// 构造函数参数constructor(public fn: () => T, // 副作用函数public scheduler?: () => void // 调度函数(控制重新执行方式)) {}// 运行副作用(触发依赖收集)run() {activeEffect = this // 标记当前正在运行的 effecttry {return this.fn()} finally {activeEffect = undefined}}// 停止侦听stop() { /* 从所有依赖中移除自身 */ }
}
2. 依赖收集流程(Track)
- 数据结构:
type Dep = Set<ReactiveEffect> // 依赖集合 type TargetMap = WeakMap<Object, Map<string, Dep>> // 全局依赖存储
- 触发时机:响应式数据的
get
操作触发时。 - 流程:
- 根据响应式对象 (
target
) 和键 (key
) 找到存入targetMap
的依赖集合 (dep
)。 - 将当前活跃的
activeEffect
添加到dep
中。 - 同时将
dep
加入activeEffect.deps
(反向记录,用于 cleanup)。
- 根据响应式对象 (
3. 触发更新(Trigger)
- 触发时机:响应式数据的
set
操作时。 - 流程:
- 根据
target
和key
从targetMap
获取对应的dep
集合。 - 遍历
dep
中所有effect
:
- 如果有
scheduler
(如computed
),执行调度器(优化性能)。 - 否则直接执行
effect.run()
。
- 根据
4. 调度器(Scheduler)
- 允许控制
effect
如何重新执行:effect(() => {console.log(obj.count) }, {scheduler(effect) { // 如将 effect 推入微任务队列中异步执行queueMicrotask(effect.run) } })
- 应用场景:
watch
的异步批处理更新。computed
的值懒更新。
三、关键优化设计
1. 嵌套 Effect 栈
- 用栈结构
effectStack
跟踪嵌套的 effect:function run() {if (!effectStack.includes(this)) {try {effectStack.push((activeEffect = this))return this.fn()} finally {effectStack.pop()activeEffect = effectStack[effectStack.length - 1]}} }
- 解决问题:组件嵌套时的依赖关系混乱。
2. Cleanup 机制
- 每次 effect 执行前清理旧依赖:
function run() {cleanup(this) // 清理之前收集的旧依赖// ...然后重新收集新依赖 }
- 解决问题:动态分支逻辑导致的无效依赖(如
v-if
切换导致的条件依赖)。
- 解决问题:动态分支逻辑导致的无效依赖(如
3. Lazy 执行
- 可配置不立即执行 effect:
const runner = effect(fn, { lazy: true }) runner() // 手动执行
- 应用场景:
computed
属性初始化时延迟计算。
- 应用场景:
四、与 Vue 各组件的关联
1. 组件渲染
- 组件
render
函数被包裹在effect
中:function setupRenderEffect(instance) {effect(() => {const subTree = instance.render.call(instance.proxy)patch(instance.subTree, subTree)instance.subTree = subTree}, { scheduler: queueJob }) // 异步更新队列 }
2. Computed 实现
computed
通过effect
+ 调度器实现懒更新:const computedRef = new ComputedRefImpl(getter,() => { // 调度器if (!this._dirty) {this._dirty = truetrigger(this, 'set', 'value')}} )
3. Watch API
watch
基于effect
的调度器实现异步回调:function watch(source, cb, { flush } = {}) {let schedulerif (flush === 'sync') {scheduler = cb} else { // 'post' 或其他默认情况scheduler = () => queuePostFlushCb(cb)}effect(() => traverse(source), { scheduler }) }
五、与 Vue 2 的对比
特性 | Vue 2 (Watcher) | Vue 3 (Effect) |
---|---|---|
依赖追踪 | 通过遍历数据触发 getter |
通过 Proxy/Reflect 自动追踪 |
更新粒度 | 依赖组件级检查 | 基于精确依赖的靶向更新 |
性能优化 | 需手写 pureComputed 等 |
内置自动的依赖清理和调度机制 |
内存管理 | 易产生内存泄漏(旧 Dep 引用问题) | 通过 WeakMap 自动释放无用依赖 |
六、源码流程图解
+---------------------+
| Reactive Object |
+----------+----------+│ 访问属性时▼
+---------------------+
| 触发 get 代理 +----→ track(target, key)
+---------------------+ │▲ ▼ 存储依赖关系│ +---------------------++----------+ targetMap || (WeakMap结构) |+---------+-----------+│▼+---------------------+| depsMap (Map) || (key → Dep Set) |+---------+-----------+│▼+---------------------+| dep (Set) || (存储所有关联的 effect)|+---------------------+
总结
Vue 3 的 effect
通过以下机制成为响应式系统的核心:
- Proxy 依赖收集:精确追踪响应式数据的使用。
- 调度器控制:提供灵活的回调执行方式。
- 内存安全:通过
WeakMap
自动管理依赖。 - 框架级优化:支持组件渲染、计算属性、watch 等核心功能。