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 // 标记当前正在运行的 effect
try {
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 = true
trigger(this, 'set', 'value')
}
}
)3. watch api
watch 基于 effect 的调度器实现异步回调:
function watch(source, cb, { flush } = {}) {
let scheduler
if (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 等核心功能。
到此这篇关于vue 3 effect作用与原理的文章就介绍到这了,更多相关vue 3 effect作用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论