Vue3的响应式系统基于ES6 Proxy实现,相比Vue2的Object.defineProperty方案,Proxy能拦截对象的所有操作(包括属性新增、删除、数组索引修改),从底层解决了Vue2响应式的已知限制。Composition API围绕响应式系统构建,通过ref和reactive两种方式创建响应式状态,配合effect实现依赖追踪与自动更新。
Proxy代理与响应式对象创建机制
Vue3使用reactive函数创建响应式对象,其核心是对原始对象进行Proxy包装:
function reactive(target) { // 如果已经是响应式对象则直接返回 if (target.__v_isReactive) return target return new Proxy(target, { get(target, key, receiver) { if (key === '__v_isReactive') return true track(target, key) // 依赖收集 const result = Reflect.get(target, key, receiver) // 深层响应式:对象类型的属性递归代理 if (typeof result === 'object' && result !== null) { return reactive(result) } return result }, 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 }, deleteProperty(target, key) { const hadKey = Object.prototype.hasOwnProperty.call(target, key) const result = Reflect.deleteProperty(target, key) if (hadKey && result) { trigger(target, key) } return result } })}
Proxy的get拦截器在每次属性读取时调用track函数收集依赖,set拦截器在属性修改时调用trigger函数触发更新。Reflect方法保证正确处理原型链上的属性访问。
深层响应式通过在get拦截器中递归调用reactive实现,只有被访问到的嵌套对象才会被代理,避免了初始化时递归遍历整个对象树的性能开销。
依赖收集与触发更新effect机制
Vue3的依赖追踪基于effect机制。每个响应式副作用函数被包装为effect,执行时自动收集其访问的响应式属性作为依赖。当依赖属性变化时,重新执行对应的effect。
依赖收集的核心数据结构:
// 全局依赖映射表// targetMap: target -> key -> dep(Set)const targetMap = new WeakMap()let activeEffect = null // 当前正在执行的effectfunction effect(fn) { const effectFn = () => { activeEffect = effectFn fn() // 执行副作用函数,内部访问响应式数据时触发track activeEffect = null } effectFn() return effectFn}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) // 将当前effect加入依赖集合}function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return const dep = depsMap.get(key) if (!dep) return // 复制一份避免遍历时Set被修改 const effects = new Set(dep) effects.forEach(effect => effect())}
track函数将当前正在执行的effect记录到targetMap中。targetMap使用WeakMap以target对象为键,当target被垃圾回收时,对应的依赖映射自动清理。trigger函数从targetMap中取出依赖该属性的所有effect并执行。
实际使用中的依赖追踪:
const state = reactive({ count: 0, name: 'Vue' })effect(() => { console.log(`count is ${state.count}`) // 访问state.count触发track})state.count = 1 // 修改state.count触发trigger,effect重新执行输出"count is 1"
ref与reactive实现差异与转换原理
ref用于包装基本类型值,reactive用于包装对象。ref内部通过对象化的方式实现依赖追踪:
function ref(value) { if (isRef(value)) return value const wrapper = { __v_isRef: true, get value() { track(wrapper, 'value') return value }, set value(newValue) { value = newValue trigger(wrapper, 'value') } } return wrapper}
ref通过getter/setter拦截value属性的读写,本质上是一个只有一个value属性的对象。reactive通过Proxy拦截整个对象的所有属性操作。
ref和reactive的选择标准:
// 基本类型必须用refconst count = ref(0)count.value++ // 必须通过.value访问// 对象类型推荐用reactiveconst user = reactive({ name: 'Alice', age: 25 })user.age++ // 直接访问,无需.value// reactive的解构会丢失响应性const { name, age } = user // name和age变成普通变量,不再响应式// 使用toRefs保持解构后的响应性const { name, age } = toRefs(user)name.value // 仍是响应式的
toRefs将reactive对象的每个属性转换为独立的ref,解构后各ref仍与原对象保持双向同步:
function toRefs(obj) { const result = {} for (const key in obj) { result[key] = toRef(obj, key) } return result}function toRef(obj, key) { return { __v_isRef: true, get value() { return obj[key] }, set value(newVal) { obj[key] = newVal } }}
toRef返回的ref通过getter/setter直接代理原对象的属性,不存储独立值,确保与reactive对象的双向绑定。
computed计算属性与缓存优化实现
computed基于effect实现,增加缓存机制避免重复计算:
function computed(getter) { let value let dirty = true // 脏标记 const effectFn = effect(getter, { lazy: true, // 不立即执行 scheduler() { if (!dirty) { dirty = true // 依赖变化时标记为脏 trigger(wrapper, 'value') // 通知依赖computed的effect } } }) const wrapper = { __v_isRef: true, get value() { if (dirty) { value = effectFn() // 重新计算 dirty = false } track(wrapper, 'value') return value } } return wrapper}
computed通过dirty标记控制缓存。首次访问时执行getter计算值并标记为clean。当getter的依赖变化时,scheduler将dirty标记为true但不立即重算。下次访问.value时检查dirty标记,为true才重新计算。这种懒求值策略避免了对未被访问的computed属性进行无意义的计算。
scheduler参数是effect的调度器配置,当依赖变化时调用scheduler而非直接重新执行effect。Vue利用scheduler实现computed的缓存失效通知、watch的异步回调等功能。
实际使用:
const price = ref(100)const quantity = ref(5)const total = computed(() => price.value * quantity.value)console.log(total.value) // 500,执行计算console.log(total.value) // 500,命中缓存不重算quantity.value = 10console.log(total.value) // 1000,dirty为true,重新计算
computed的缓存机制在计算开销大的场景(如过滤大数组、复杂运算)中能有效减少重复计算,只有当依赖的响应式数据变化时才触发重新计算。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3compositionapi-xiang-ying-shi-xi-tong-yuan-li-proxy-dai/