Vue3 Composition API响应式系统原理:Proxy代理与依赖追踪机制

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/

(0)
小编小编
上一篇 9小时前
下一篇 9小时前

相关推荐