Vue3响应式原理深入剖析:Proxy与effect的依赖追踪机制

Vue3的响应式系统基于ES6 Proxy实现,相比Vue2的Object.defineProperty方案,能拦截更多操作类型且性能更优。理解Proxy与effect的配合机制,有助于在复杂场景下编写高效的前端代码,避免不必要的重复渲染。

Proxy拦截器与传统defineProperty的差异

Vue2使用Object.defineProperty劫持对象属性,存在三个固有限制:无法监听属性新增/删除(需用Vue.set/Vue.delete)、无法监听数组索引修改、需要递归遍历对象所有属性初始化劫持。Vue3改用Proxy构造函数,在对象层面整体代理,天然支持属性增删和数组操作,且采用惰性代理——只有被访问的属性才会递归转为响应式。

reactive函数的代理实现

Vue3的reactive函数返回目标对象的Proxy代理。核心拦截逻辑如下:

const targetMap = new WeakMap()  // 存储依赖关系
let activeEffect = null  // 当前正在执行的effect

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      track(target, key)  // 依赖收集
      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 result = Reflect.deleteProperty(target, key)
      trigger(target, key)
      return result
    }
  }
  return new Proxy(target, handler)
}

WeakMap的key是原始对象,value是Map。Map的key是属性名,value是Set集合,存储该属性关联的所有effect函数。WeakMap的选择是为了在原始对象被GC回收时自动清理依赖。

effect函数与依赖收集

effect函数包裹一个回调,执行时把自身设为activeEffect,执行回调过程中触发属性get操作时完成依赖收集:

function effect(fn) {
  const _effect = function() {
    activeEffect = _effect
    fn()
    activeEffect = null
  }
  _effect()
  return _effect
}

function track(target, key) {
  if (activeEffect === null) return
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

当属性被修改时,trigger函数取出该属性关联的effect集合并逐一执行,实现数据变更到视图更新的自动驱动。Vue3内部用effect实现了computed、watch和组件render。

ref与reactive的选择策略

ref和reactive都能创建响应式数据,但适用场景不同:

// ref:适合基本类型,通过.value访问
const count = ref(0)
count.value++

// reactive:适合对象/数组,直接访问属性
const state = reactive({
  user: { name: '张三', age: 25 },
  list: [1, 2, 3]
})
state.user.name = '李四'  // 响应式生效
state.list.push(4)  // 响应式生效

// ref持有对象时内部自动用reactive代理
const userRef = ref({ name: '张三' })
userRef.value.name = '李四'  // 响应式生效

// 解构reactive会丢失响应式
const { user } = state
user.name = '王五'  // 响应式丢失

// 使用toRefs保持解构后的响应式
const { user } = toRefs(state)
user.value.name = '王五'  // 响应式保持

工程实践中,推荐单一对象用reactive,零散的基本类型用ref。组件内状态管理推荐把相关状态归入同一个reactive对象,减少解构带来的响应式丢失风险。

computed缓存机制分析

computed基于effect实现,但加入了缓存和脏标记机制。只有依赖值变化时才重新计算:

function computed(getter) {
  let value, dirty = true

  const _effect = effect(() => {
    if (dirty) {
      value = getter()
      dirty = false
    }
  }, {
    scheduler: () => {
      dirty = true
      trigger(computedObj, 'value')
    }
  })

  const computedObj = {
    get value() {
      track(computedObj, 'value')
      if (dirty) {
        value = getter()
        dirty = false
      }
      return value
    }
  }
  return computedObj
}

scheduler选项让effect在被trigger时执行自定义逻辑而非重新运行effect。computed利用这一点实现lazy evaluation——只有访问.value且dirty为true时才重新计算。高开销计算场景下,computed比methods性能更优。

响应式性能优化建议

大型应用中响应式性能需关注以下几点。避免在template中创建过多响应式对象;大列表渲染优先用shallowRef避免深层代理开销;只读数据用readonly包裹后不会被代理。组件库设计时,合理使用markRaw跳过代理,对第三方库实例(地图、图表对象等)能显著减少内存占用。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-xiang-ying-shi-yuan-li-shen-ru-pou-xi-proxy-yu-effect/

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

相关推荐