02-Vue3 响应式系统原理
Vue3 响应式系统原理
深入解析 Vue3 基于 Proxy 的响应式系统,从 reactive/ref 的实现到 track/trigger 机制,从 ReactiveEffect 到 Computed 原理,全面理解 Vue3 响应式的设计精髓。
一、前言
Vue3 的响应式系统是一次彻底的架构重构。从 Object.defineProperty 到 Proxy,从 Dep/Watcher 到 track/trigger + ReactiveEffect,不仅解决了 Vue2 的诸多局限,还带来了更好的性能和更灵活的扩展能力。
本章将深入 @vue/reactivity 包的源码,剖析 Vue3 响应式系统的完整实现。
二、Proxy 替代 Object.defineProperty
2.1 为什么 Vue3 选择 Proxy
Vue2 的 Object.defineProperty 存在根本性局限,而 Proxy 作为 ES6 引入的元编程特性,可以完美解决这些问题:
| 能力 | Object.defineProperty | Proxy |
|---|---|---|
| 新增属性 | 不支持 | 支持 |
| 删除属性 | 不支持 | 支持 |
| 数组索引 | 不支持 | 支持 |
| 数组 length | 不支持 | 支持 |
in 操作符 |
不支持 | 支持 |
Object.keys() |
不支持 | 支持 |
| Map/Set/WeakMap | 不支持 | 支持 |
| 嵌套对象 | 需递归遍历 | 懒代理(访问时才代理) |
2.2 Proxy 基础用法
const target = { name: 'Vue', count: 0 }
const proxy = new Proxy(target, {
get(target, key, receiver) {
console.log('读取:', key)
// receiver 保证正确的 this 指向
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('设置:', key, '=', value)
return Reflect.set(target, key, value, receiver)
},
has(target, key) {
console.log('in 操作:', key)
return Reflect.has(target, key)
},
deleteProperty(target, key) {
console.log('删除:', key)
return Reflect.deleteProperty(target, key)
},
ownKeys(target) {
console.log('遍历 keys')
return Reflect.ownKeys(target)
}
})
proxy.name // 读取: name
proxy.count = 1 // 设置: count = 1
'name' in proxy // in 操作: name
Object.keys(proxy) // 遍历 keys
delete proxy.name // 删除: name
2.3 Vue3 中的 createReactiveObject
// Vue3 源码:packages/reactivity/src/reactive.ts
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 非对象类型不处理
if (!isObject(target)) {
return target
}
// 已经是代理对象,直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 目标对象已经是代理(避免重复代理)
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建 Proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 缓存代理对象
proxyMap.set(target, proxy)
return proxy
}
三、reactive 与 ref 的实现
3.1 reactive 的实现
// Vue3 源码:packages/reactivity/src/reactive.ts
export function reactive(target) {
return createReactiveObject(
target,
false,
mutableHandlers, // 普通对象的 handler
mutableCollectionHandlers, // Map/Set/WeakMap/WeakSet 的 handler
reactiveMap
)
}
// mutableHandlers 的核心实现
const mutableHandlers = {
get(target, key, receiver) {
// 特殊 key 的处理
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
}
if (key === ReactiveFlags.RAW) {
return target
}
// 对数组特殊方法的处理(includes, indexOf, lastIndexOf)
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
// 依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 懒代理:访问时才递归代理
if (isShallow) {
return res
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
},
set(target, key, value, receiver) {
const oldValue = target[key]
// 判断是新增属性还是修改属性
const hadKey = isArray(target)
? isIntegerKey(key) && key < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// 触发更新
if (!hadKey) {
// 新增属性
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 修改属性
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
},
has(target, key) {
const result = Reflect.has(target, key)
track(target, TrackOpTypes.HAS, key)
return result
},
ownKeys(target) {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
}
3.2 ref 的实现
// Vue3 源码:packages/reactivity/src/ref.ts
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
// 依赖收集
trackRefValue(this)
return this._value
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
// 触发更新
triggerRefValue(this)
}
}
}
export function ref(value) {
return createRef(value, false)
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
// trackRefValue 和 triggerRefValue
export function trackRefValue(ref) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
export function triggerRefValue(ref) {
ref = toRaw(ref)
if (ref.dep) {
triggerEffects(ref.dep)
}
}
四、track/trigger 机制
4.1 依赖存储数据结构
Vue3 使用三层数据结构来存储依赖关系:
// 全局依赖存储
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
4.2 track 的实现
// Vue3 源码:packages/reactivity/src/effect.ts
export function track(target, type, key) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
trackEffects(dep)
}
}
export function trackEffects(dep) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
// 使用位运算优化去重
if (!newTracked(dep)) {
dep.n |= trackOpBit
shouldTrack = !wasTracked(dep)
}
} else {
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect.deps.push(dep)
}
}
4.3 trigger 的实现
// Vue3 源码:packages/reactivity/src/effect.ts
export function trigger(target, type, key, newValue, oldValue) {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
let deps: (Dep | undefined)[] = []
// 收集需要触发的 effect
if (key === 'length' && isArray(target)) {
// 数组 length 变化,需要触发所有大于新 length 的索引
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
// 特定 key 的依赖
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// 迭代相关依赖(for...in, Object.keys)
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
// 去重并触发
if (deps.length === 1) {
if (deps[0]) {
triggerEffects(deps[0])
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
triggerEffects(createDep(effects))
}
}
export function triggerEffects(dep) {
const effects = isArray(dep) ? dep : [...dep]
// 先执行计算属性的 effect(清理依赖)
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect)
}
}
// 再执行普通 effect
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect)
}
}
}
function triggerEffect(effect) {
if (effect !== activeEffect || effect.allowRecurse) {
if (effect.scheduler) {
effect.scheduler() // 使用调度器(如组件更新)
} else {
effect.run() // 直接执行
}
}
}
五、ReactiveEffect
5.1 ReactiveEffect 的核心实现
ReactiveEffect 是 Vue3 中替代 Vue2 Watcher 的核心类:
// Vue3 源码:packages/reactivity/src/effect.ts
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
computed?: ComputedRefImpl<T>
allowRecurse?: boolean
private deferStop?: boolean
onStop?: () => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
cleanupEffect(this)
}
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
if (this.deferStop) {
this.stop()
}
}
}
stop() {
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
5.2 effect 函数
export function effect(fn, options) {
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
六、Computed 原理
6.1 Computed 的核心实现
// Vue3 源码:packages/reactivity/src/computed.ts
class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean = false
public _dirty = true
constructor(
getter,
private readonly _setter,
isReadonly: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
// 调度器:当依赖变化时执行
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this.effect.computed = this
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
const self = toRaw(this)
trackRefValue(self)
if (self._dirty) {
self._dirty = false
self._value = self.effect.run()
}
return self._value
}
set value(newValue) {
this._setter(newValue)
}
}
export function computed(getterOrOptions) {
let getter, setter
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = () => {
console.warn('Write operation failed: computed value is readonly')
}
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(
getter,
setter,
onlyGetter || !setter
)
}
6.2 Computed 的缓存机制
七、嵌套 effect 处理
7.1 嵌套 effect 的场景
在 Vue3 中,effect 可以嵌套执行。例如,组件渲染 effect 中可能包含另一个 effect:
// 外层 effect(组件渲染)
effect(() => {
console.log('outer:', state.outer)
// 内层 effect(可能在某个 hook 中)
effect(() => {
console.log('inner:', state.inner)
})
})
7.2 Vue3 的解决方案
Vue3 使用栈结构来管理嵌套的 effect:
// 全局变量
let activeEffect: ReactiveEffect | undefined
let shouldTrack = true
class ReactiveEffect {
parent: ReactiveEffect | undefined = undefined
run() {
if (!this.active) {
return this.fn()
}
// 检查循环依赖
let parent: ReactiveEffect | undefined = activeEffect
while (parent) {
if (parent === this) {
return // 避免无限循环
}
parent = parent.parent
}
try {
// 将当前 effect 设为 active,保存父 effect
this.parent = activeEffect
activeEffect = this
shouldTrack = true
return this.fn()
} finally {
// 恢复父 effect
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
}
}
7.3 嵌套 effect 的依赖收集
八、依赖清理
8.1 为什么需要依赖清理
在条件渲染场景中,某些依赖可能只在特定条件下被访问。当条件变化时,旧的依赖应该被清理:
const state = reactive({ show: true, name: 'Vue' })
effect(() => {
if (state.show) {
console.log(state.name) // 只在这个条件下依赖 name
}
})
// 初始时:effect 依赖 show 和 name
state.show = false
// 现在 effect 只依赖 show,但 name 的 dep 中还保存着这个 effect
// 如果不清理,修改 name 也会触发 effect,造成不必要的更新
8.2 Vue3 的依赖清理机制
Vue3 使用位标记算法来高效清理依赖:
// 位标记相关变量
let effectTrackDepth = 0
let trackOpBit = 1
const maxMarkerBits = 30
// 每个 dep 有两个位标记字段
// dep.w: 上一次 run 时收集的 effect 的位标记
// dep.n: 当前 run 时收集的 effect 的位标记
function initDepMarkers({ deps }) {
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].w |= trackOpBit // 标记为"旧依赖"
}
}
}
function finalizeDepMarkers(effect) {
const { deps } = effect
if (deps.length) {
let ptr = 0
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
if (wasTracked(dep) && !newTracked(dep)) {
// 旧依赖但当前没有收集到,需要清理
dep.delete(effect)
} else {
deps[ptr++] = dep
}
// 清除位标记
dep.w &= ~trackOpBit
dep.n &= ~trackOpBit
}
deps.length = ptr
}
}
function wasTracked(dep) {
return (dep.w & trackOpBit) > 0
}
function newTracked(dep) {
return (dep.n & trackOpBit) > 0
}
8.3 依赖清理的完整流程
九、Mermaid 图表:Vue3 响应式系统全景
十、代码示例
10.1 手写简化版 Vue3 响应式系统
// 简化版 Vue3 响应式系统
// 全局状态
let activeEffect = null
const targetMap = new WeakMap()
// track:依赖收集
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)
activeEffect.deps.push(dep)
}
// trigger:触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
const effects = new Set(dep)
effects.forEach(effect => {
if (effect !== activeEffect) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
})
}
}
// reactive
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
track(target, key)
// 懒代理
if (typeof res === 'object' && res !== null) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
trigger(target, key)
}
return result
}
})
}
// ref
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newValue) {
if (newValue !== value) {
value = newValue
trigger(refObject, 'value')
}
}
}
return refObject
}
// effect
function effect(fn, options = {}) {
const effect = {
run() {
activeEffect = effect
effect.deps = []
const result = fn()
activeEffect = null
return result
},
deps: [],
scheduler: options.scheduler
}
if (!options.lazy) {
effect.run()
}
return effect
}
// computed
function computed(getter) {
let value
let dirty = true
const runner = effect(getter, {
lazy: true,
scheduler() {
dirty = true
}
})
return {
get value() {
if (dirty) {
value = runner.run()
dirty = false
}
return value
}
}
}
// 使用示例
const state = reactive({ count: 0, name: 'Vue' })
const double = computed(() => state.count * 2)
effect(() => {
console.log('count:', state.count, 'double:', double.value)
})
state.count++ // 触发更新
state.name = 'Vue3' // 不影响上面的 effect
10.2 Vue3 响应式 API 对比演示
import { reactive, ref, computed, watch, effectScope } from 'vue'
// reactive:对象响应式
const state = reactive({
user: { name: '张三', age: 20 },
items: [1, 2, 3]
})
// 所有操作都能被检测
state.user.name = '李四' // ✅ 触发更新
state.items.push(4) // ✅ 触发更新
state.items[0] = 100 // ✅ 触发更新(Vue3 支持!)
delete state.user.age // ✅ 触发更新(Vue3 支持!)
// ref:基本类型 + 对象
const count = ref(0)
const user = ref({ name: '张三' })
// computed:计算属性(带缓存)
const fullName = computed(() => {
console.log('computed 执行')
return state.user.firstName + ' ' + state.user.lastName
})
// watch:侦听器
watch(() => state.user.age, (newVal, oldVal) => {
console.log('age变化:', oldVal, '->', newVal)
})
// effectScope:批量停止 effect
const scope = effectScope()
scope.run(() => {
effect(() => console.log(state.count))
effect(() => console.log(state.name))
})
// 停止 scope 内所有 effect
scope.stop()
十一、常见问题
Q1:Vue3 的 Proxy 能兼容 IE 吗?
不能。Proxy 是 ES6 特性,无法通过 polyfill 完全模拟。Vue3 官方不支持 IE11。如果需要兼容 IE,可以使用 Vue2 或 Vue3 的兼容构建版本(功能受限)。
Q2:reactive 和 ref 在底层有什么关联?
ref 在内部会调用 reactive。当 ref 的值是对象时,会通过 toReactive 将其转换为 reactive:
// ref 内部
this._value = isShallow ? value : toReactive(value)
// toReactive
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
Q3:为什么 Vue3 的 effect 比 Vue2 的 Watcher 更灵活?
ReactiveEffect 是一个更底层的抽象,它:
- 不绑定特定的更新逻辑(通过 scheduler 自定义)
- 支持嵌套和递归
- 支持 effectScope 批量管理
- 可以独立使用(不依赖 Vue 组件)
Q4:Vue3 如何处理 Map 和 Set?
Vue3 为集合类型提供了专门的 handlers,拦截 get、set、add、delete、has、clear、forEach、keys、values、entries 等方法:
const map = reactive(new Map())
map.set('key', 'value') // ✅ 触发更新
map.get('key') // ✅ 依赖收集
map.has('key') // ✅ 依赖收集
map.delete('key') // ✅ 触发更新
Q5:位标记算法清理依赖的优势是什么?
相比 Vue2 每次创建新的 Set/数组来对比新旧依赖,Vue3 的位标记算法:
- 时间复杂度更低(O(n) 且常数更小)
- 内存分配更少(不需要创建临时数据结构)
- 支持嵌套 effect 的依赖追踪
十二、总结
Vue3 的响应式系统是一次从底层到 API 的全面升级:
| 对比项 | Vue2 | Vue3 |
|---|---|---|
| 拦截方式 | Object.defineProperty |
Proxy |
| 核心类 | Observer / Dep / Watcher |
ReactiveEffect + track/trigger |
| 新增属性 | 不支持(需 Vue.set) |
原生支持 |
| 数组索引 | 不支持 | 原生支持 |
| 删除属性 | 不支持(需 Vue.delete) |
原生支持 |
| Map/Set | 不支持 | 原生支持 |
| 嵌套对象 | 递归初始化 | 懒代理 |
| 依赖清理 | 创建新数组对比 | 位标记算法 |
| 嵌套 effect | 有限支持 | 完整支持 |
Vue3 响应式的核心设计思想:
- 懒代理:访问时才递归代理深层对象,减少初始化开销
- 精确追踪:通过
TrackOpTypes和TriggerOpTypes区分不同操作类型 - 高效清理:位标记算法实现 O(n) 的依赖清理
- 灵活调度:通过
scheduler支持自定义更新策略 - 独立复用:
@vue/reactivity可以独立使用,不依赖 Vue 运行时
十三、思考题
- 为什么 Vue3 选择使用
Reflect.get/set而不是直接访问target[key]? effect的scheduler参数在 Vue 组件渲染中是如何被使用的?- 设计一个场景,演示嵌套 effect 可能导致的循环依赖问题,以及 Vue3 是如何避免的。
- 对比 Vue2 的
Dep.target栈和 Vue3 的activeEffect父子链,分析各自的优劣。 - 如果让你基于 Vue3 的
@vue/reactivity实现一个简单的状态管理库,你会如何设计?
更多推荐



所有评论(0)