2344 字
12 分钟
Vue 3 Reactive 响应式原理深度解析

为什么对象需要 Proxy 代理?#

对象响应式的挑战#

与原始值不同,对象本身可以被拦截,但 Vue 需要解决更复杂的问题:

// 对象的多种操作需要被拦截
const obj = { count: 0, user: { name: 'vue' } }
// 1. 属性读取
console.log(obj.count) // 需要依赖收集
console.log(obj.user.name) // 嵌套对象也需要响应式
// 2. 属性设置
obj.count = 10 // 需要触发更新
obj.user.name = 'react' // 嵌套属性修改也需要响应式
// 3. 属性删除
delete obj.count // 需要触发更新
// 4. 属性枚举
for (let key in obj) {} // 需要建立依赖关系
// 5. 动态属性添加
obj.newProp = 'value' // 需要触发更新

核心挑战:对象操作种类繁多,需要全面拦截各种操作类型。

ReactiveEffect 类的内部实现#

Vue 3 使用 ReactiveEffect 类管理副作用函数:

class ReactiveEffect {
constructor(fn, scheduler = null) {
this.fn = fn // 副作用函数
this.scheduler = scheduler // 调度器(用于批量更新)
this.active = true // 是否激活
this.deps = [] // 该effect依赖的响应式数据集合
this._trackId = 0 // 用于依赖清理的追踪ID
this._depsLength = 0 // 依赖数量
}
run() {
if (!this.active) {
return this.fn()
}
let parent = activeEffect
let lastShouldTrack = shouldTrack
try {
// 设置当前effect为活跃状态
activeEffect = this
shouldTrack = true
// 清理旧依赖
this.cleanupEffect()
// 执行副作用函数,过程中会收集新依赖
return this.fn()
} finally {
// 恢复上一个effect
activeEffect = parent
shouldTrack = lastShouldTrack
}
}
stop() {
if (this.active) {
this.cleanupEffect()
this.active = false
}
}
cleanupEffect() {
// 从所有依赖的响应式数据中移除当前effect
for (let i = 0; i < this.deps.length; i++) {
this.deps[i].delete(this)
}
this.deps.length = 0
}
}

Proxy 处理器的完整实现#

Vue 3 的 reactive() 基于 Proxy 实现,需要处理多种操作:

const baseHandlers = {
// 属性读取拦截
get(target, key, receiver) {
// 特殊key处理
if (key === ReactiveFlags.IS_REACTIVE) return true
if (key === ReactiveFlags.RAW) return target
const res = Reflect.get(target, key, receiver)
// 依赖收集
track(target, TrackOpTypes.GET, key)
// 如果是对象,递归创建响应式代理
if (isObject(res)) {
return reactive(res)
}
return res
},
// 属性设置拦截
set(target, key, value, receiver) {
let oldValue = target[key]
// 判断是新增还是修改
const hadKey = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// 避免触发原型链上的setter
if (target === toRaw(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 (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
},
// 属性存在性检查拦截
has(target, key) {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
},
// 属性枚举拦截
ownKeys(target) {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
}

数组特殊处理#

数组需要特殊的响应式处理:

const arrayInstrumentations = {}
// 重写数组的变异方法
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(key => {
arrayInstrumentations[key] = function (...args) {
// 暂停依赖收集,避免无限递归
pauseTracking()
// 调用原始方法
const res = Array.prototype[key].apply(this, args)
// 恢复依赖收集
resetTracking()
return res
}
})
// 重写数组的查找方法,确保响应式对象的正确比较
;['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
arrayInstrumentations[key] = function (...args) {
const arr = toRaw(this)
// 建立对数组每个元素的依赖
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// 先用原始参数查找
const res = arr[key](...args)
if (res === -1 || res === false) {
// 如果没找到,尝试用原始值查找
return arr[key](...args.map(toRaw))
} else {
return res
}
}
})

深度响应式机制#

递归代理的实现#

function createReactiveObject(target, baseHandlers, collectionHandlers, proxyMap) {
// 如果已经是代理对象,直接返回
if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// 如果已经有对应的代理,返回缓存的代理
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建新的代理对象
const proxy = new Proxy(
target,
isCollectionType(target) ? collectionHandlers : baseHandlers
)
// 缓存代理对象
proxyMap.set(target, proxy)
return proxy
}
// 递归响应式示例
const state = reactive({
user: {
profile: {
name: 'vue',
settings: {
theme: 'dark'
}
}
}
})
// 访问深层属性时的内部流程:
// 1. state.user -> get拦截器 -> track(state, 'user') -> 返回 reactive(userObj)
// 2. userProxy.profile -> get拦截器 -> track(userObj, 'profile') -> 返回 reactive(profileObj)
// 3. profileProxy.name -> get拦截器 -> track(profileObj, 'name') -> 返回 'vue'

集合类型的特殊处理#

Map、Set、WeakMap、WeakSet 需要特殊的响应式处理:

const collectionHandlers = {
get: createInstrumentationGetter(false, false)
}
function createInstrumentationGetter(isReadonly, shallow) {
const instrumentations = shallow
? shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
return (target, key, receiver) => {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
// Map/Set 方法的响应式包装
const mutableInstrumentations = {
get(key) {
return get(this, key)
},
set(key, value) {
return set.call(this, key, value)
},
delete(key) {
return deleteEntry.call(this, key)
},
clear() {
return clear.call(this)
},
forEach: createForEach(false, false)
}
function get(target, key) {
target = toRaw(target)
const rawKey = toRaw(key)
if (key !== rawKey) {
track(target, TrackOpTypes.GET, key)
}
track(target, TrackOpTypes.GET, rawKey)
const { has } = getProto(target)
const wrap = isShallow ? toShallow : toReactive
if (has.call(target, key)) {
return wrap(target.get(key))
} else if (has.call(target, rawKey)) {
return wrap(target.get(rawKey))
}
}

性能优化策略#

1. 代理缓存机制#

// 全局代理缓存
const reactiveMap = new WeakMap()
const shallowReactiveMap = new WeakMap()
const readonlyMap = new WeakMap()
const shallowReadonlyMap = new WeakMap()
function reactive(target) {
// 如果是只读对象,直接返回
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false, // isReadonly
false, // isShallow
reactiveMap
)
}
// 缓存避免重复代理
const obj = { count: 0 }
const proxy1 = reactive(obj) // 创建新代理,缓存到 reactiveMap
const proxy2 = reactive(obj) // 从缓存返回,proxy1 === proxy2

2. 依赖收集优化#

// 避免重复收集的优化
let shouldTrack = true
const trackStack = []
export function pauseTracking() {
trackStack.push(shouldTrack)
shouldTrack = false
}
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
// 使用示例
function expensiveOperation() {
pauseTracking() // 暂停依赖收集
// 大量响应式数据访问,但不需要建立依赖
for (let i = 0; i < 10000; i++) {
someReactiveData.value++
}
resetTracking() // 恢复依赖收集
}

3. 批量更新机制#

const queue = []
let isFlushing = false
let isFlushPending = false
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job)
}
queueFlush()
}
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
// 使用微任务批量执行更新
Promise.resolve().then(flushJobs)
}
}
function flushJobs() {
isFlushPending = false
isFlushing = true
// 按优先级排序
queue.sort((a, b) => getId(a) - getId(b))
try {
for (let i = 0; i < queue.length; i++) {
queue[i]()
}
} finally {
queue.length = 0
isFlushing = false
}
}

reactive 与 ref 的对比#

适用场景对比#

// 1. 原始值 - 只能用 ref
const count = ref(0)
const message = ref('hello')
const isLoading = ref(false)
// 2. 对象 - 两种方式都可以
const userRef = ref({ name: 'vue', age: 3 })
const userReactive = reactive({ name: 'vue', age: 3 })
// 访问方式不同
console.log(userRef.value.name) // 需要 .value
console.log(userReactive.name) // 直接访问
// 3. 数组 - 推荐使用 reactive
const listRef = ref([1, 2, 3])
const listReactive = reactive([1, 2, 3])
listRef.value.push(4) // 需要 .value
listReactive.push(4) // 直接操作

内部实现差异#

// ref 的结构
const refValue = ref({ count: 0 })
/*
RefImpl {
_value: Proxy({ count: 0 }), // 内部用 reactive 包装对象
_rawValue: { count: 0 }, // 原始值
__v_isRef: true, // ref 标识
dep: Set(), // 依赖收集器
get value() { ... }, // 单一入口
set value(val) { ... }
}
*/
// reactive 的结构
const reactiveValue = reactive({ count: 0 })
/*
Proxy {
[[Handler]]: baseHandlers, // Proxy 处理器
[[Target]]: { count: 0 }, // 原始对象
// 每个属性都有独立的依赖收集
}
*/

性能特征对比#

function performanceComparison() {
const iterations = 100000
// ref 访问嵌套属性
const refData = ref({ a: { b: { c: 1 } } })
console.time('ref nested access')
for (let i = 0; i < iterations; i++) {
const val = refData.value.a.b.c // .value + 三次属性访问
}
console.timeEnd('ref nested access') // ~120ms
// reactive 访问嵌套属性
const reactiveData = reactive({ a: { b: { c: 1 } } })
console.time('reactive nested access')
for (let i = 0; i < iterations; i++) {
const val = reactiveData.a.b.c // 三次代理拦截
}
console.timeEnd('reactive nested access') // ~100ms
}
// 性能总结:
// - ref: 单一依赖入口,但访问对象属性需要 .value
// - reactive: 每个属性独立依赖,更精细的更新控制

使用建议#

// ✅ 推荐用法
const count = ref(0) // 原始值用 ref
const user = reactive({ // 对象用 reactive
name: 'vue',
profile: { age: 3 }
})
// ❌ 不推荐用法
const countObj = reactive({ value: 0 }) // 原始值包装成对象用 reactive
const userRef = ref({ // 对象用 ref(需要 .value)
name: 'vue',
profile: { age: 3 }
})
// 🤔 特殊场景
const data = ref(null) // 初始值为 null,后续可能是对象
// data.value = { user: 'info' } // 动态赋值不同类型

总结#

reactive 的核心特点#

  1. Proxy 代理:拦截对象的所有操作类型(get、set、delete、has、ownKeys)
  2. 深度响应式:自动递归处理嵌套对象
  3. 精细依赖收集:每个属性独立收集依赖
  4. 特殊类型支持:Map、Set、WeakMap、WeakSet 的专门处理

设计优势#

  1. 操作拦截全面:相比 Vue 2 的 Object.defineProperty,Proxy 能拦截更多操作
  2. 动态属性支持:新增/删除属性都能被响应式系统感知
  3. 更好的数组支持:不需要特殊处理数组的变异方法
  4. 集合类型支持:原生支持 Map、Set 等集合类型

关键理解#

  • reactive 解决了对象的全面响应式拦截问题
  • 通过 Proxy 代理,实现了对对象操作的完整控制
  • ref 形成互补,提供了完整的响应式解决方案
  • 性能优化通过缓存、批量更新等机制保证了实用性

reactive 系统是 Vue 3 响应式架构的另一重要支柱,与 ref 共同构建了强大而灵活的响应式生态。

Vue 3 Reactive 响应式原理深度解析
https://fuwari.vercel.app/posts/vue-reactive/
作者
Lorem Ipsum
发布于
2025-09-11
许可协议
CC BY-NC-SA 4.0