深入理解 Vue 3 响应式系统
从 Proxy 拦截到 effect 调度,一步步拆解 Vue 3 reactivity 包的核心实现。
2 分钟阅读327 字
一、概述
Vue 3 的响应式系统由 @vue/reactivity 包承载,仅约 1000 行 TypeScript,却支撑起了整个框架的运行时。本文带你从 reactive 入手,理解依赖收集、派发更新与调度器之间的协作。
二、背景
在 Vue 2 中,响应式基于 Object.defineProperty 实现,存在无法监听新增属性、数组下标等缺陷。Vue 3 改用 ES2015 Proxy,从语言层面解决了上述痛点,并天然支持 Map / Set / WeakMap / WeakSet。
三、核心实现
3.1 reactive:创建代理
TS
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> {
if (isReadonly(target)) return target as any
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
createReactiveObject 会缓存已代理的对象,避免重复包装。
3.2 track & trigger
TS
export function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) targetMap.set(target, (depsMap = new Map()))
let dep = depsMap.get(key)
if (!dep) depsMap.set(key, (dep = new Set()))
dep.add(activeEffect)
}
export function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach((effect) => queueJob(effect.scheduler ? effect.scheduler : effect))
}
3.3 effect 调度
调度器 queueJob 会把 effect 推入微任务队列,同一 tick 内多次更新只会触发一次回调,这是 Vue 3 性能优于 Vue 2 的关键之一。
TS
let queue: Function[] = []
const resolvedPromise = Promise.resolve()
let flushPromise: Promise<void> | null = null
export function queueJob(job: Function) {
if (!queue.includes(job)) queue.push(job)
if (!flushPromise) {
flushPromise = resolvedPromise.then(flushJobs)
}
}
四、实践技巧
- 避免深响应:扁平数据结构用
shallowRef/shallowReactive,配合triggerRef主动通知。 - 解构会丢失响应性:使用
toRefs(state)保持解构后的属性仍是 ref。 - 计算属性 vs effect:能用
computed就不要写effect,前者自带缓存。
五、总结
Vue 3 的响应式系统是一套基于 Proxy + 依赖收集 + 任务调度的最小完备实现。理解它能帮助我们写出更高效的组件,并避免常见陷阱。
文章 slug: vue3-reactivity-deep-dive
最后更新: 2026-06-12
版权声明 · CC BY-NC-ND 4.0
署名-非商业性使用-禁止演绎 4.0 国际
评论
由 GitHub Discussions 驱动