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) // 创建新代理,缓存到 reactiveMapconst proxy2 = reactive(obj) // 从缓存返回,proxy1 === proxy22. 依赖收集优化
// 避免重复收集的优化let shouldTrack = trueconst 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 = falselet 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. 原始值 - 只能用 refconst 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) // 需要 .valueconsole.log(userReactive.name) // 直接访问
// 3. 数组 - 推荐使用 reactiveconst listRef = ref([1, 2, 3])const listReactive = reactive([1, 2, 3])
listRef.value.push(4) // 需要 .valuelistReactive.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) // 原始值用 refconst user = reactive({ // 对象用 reactive name: 'vue', profile: { age: 3 }})
// ❌ 不推荐用法const countObj = reactive({ value: 0 }) // 原始值包装成对象用 reactiveconst userRef = ref({ // 对象用 ref(需要 .value) name: 'vue', profile: { age: 3 }})
// 🤔 特殊场景const data = ref(null) // 初始值为 null,后续可能是对象// data.value = { user: 'info' } // 动态赋值不同类型总结
reactive 的核心特点
- Proxy 代理:拦截对象的所有操作类型(get、set、delete、has、ownKeys)
- 深度响应式:自动递归处理嵌套对象
- 精细依赖收集:每个属性独立收集依赖
- 特殊类型支持:Map、Set、WeakMap、WeakSet 的专门处理
设计优势
- 操作拦截全面:相比 Vue 2 的
Object.defineProperty,Proxy 能拦截更多操作 - 动态属性支持:新增/删除属性都能被响应式系统感知
- 更好的数组支持:不需要特殊处理数组的变异方法
- 集合类型支持:原生支持 Map、Set 等集合类型
关键理解
- reactive 解决了对象的全面响应式拦截问题
- 通过 Proxy 代理,实现了对对象操作的完整控制
- 与 ref 形成互补,提供了完整的响应式解决方案
- 性能优化通过缓存、批量更新等机制保证了实用性
reactive 系统是 Vue 3 响应式架构的另一重要支柱,与 ref 共同构建了强大而灵活的响应式生态。
Vue 3 Reactive 响应式原理深度解析
https://fuwari.vercel.app/posts/vue-reactive/