Vue3 Composition API响应式系统原理与reactive ref实现机制

Vue3响应式系统从Object.defineProperty到Proxy的演进

Vue2的响应式系统基于Object.defineProperty实现,通过getter/setter劫持对象属性的读写操作。这种方案存在已知局限:无法检测属性的新增和删除,无法监听数组索引变化和长度修改,需要$set$delete等辅助API。深层对象需要递归遍历,初始化时性能开销大。

Vue3采用ES6 Proxy重写了整个响应式系统。Proxy可以代理整个对象的所有操作,包括属性新增、删除、数组索引修改等,不需要递归遍历。配合Reflect API,响应式系统的代码更简洁、性能更优、覆盖场景更完整。

reactive函数的实现原理与Proxy拦截

reactive是Vue3响应式系统的核心函数,将普通对象转换为响应式代理对象。简化后的实现逻辑:

// Vue3 reactive 简化实现
const reactiveMap = new WeakMap()

function reactive(target) {
  // 原始值不做响应式处理
  if (typeof target !== 'object' || target === null) {
    return target
  }

  // 如果已经是响应式对象,直接返回
  if (target.__v_isReactive) {
    return target
  }

  // 缓存检查,避免重复代理
  if (reactiveMap.has(target)) {
    return reactiveMap.get(target)
  }

  const proxy = new Proxy(target, {
    get(obj, key, receiver) {
      if (key === '__v_isReactive') return true

      const result = Reflect.get(obj, key, receiver)

      // 依赖收集:记录谁在读取这个属性
      track(obj, key)

      // 深层响应式:递归代理嵌套对象(懒代理)
      if (typeof result === 'object' && result !== null) {
        return reactive(result)
      }

      return result
    },

    set(obj, key, value, receiver) {
      const oldValue = obj[key]
      const result = Reflect.set(obj, key, value, receiver)

      // 只在新旧值不同时触发更新
      if (oldValue !== value) {
        // 派发更新:通知依赖该属性的effect重新执行
        trigger(obj, key)
      }

      return result
    },

    deleteProperty(obj, key) {
      const result = Reflect.deleteProperty(obj, key)
      trigger(obj, key)
      return result
    }
  })

  reactiveMap.set(target, proxy)
  return proxy
}

关键设计点:使用WeakMap缓存代理对象,避免对同一原始对象多次创建Proxy;嵌套对象采用懒代理策略,只在首次访问时递归代理,减少初始化开销;tracktrigger分别负责依赖收集和派发更新。

ref函数与基本类型响应式的实现

reactive只能处理对象类型,基本类型(string、number、boolean)无法被Proxy代理。Vue3通过ref函数解决这一限制,将基本类型包装为对象:

// Vue3 ref 简化实现
class RefImpl {
  constructor(value) {
    this._value = isObject(value) ? reactive(value) : value
    this.__v_isRef = true
  }

  get value() {
    // 依赖收集
    track(this, 'value')
    return this._value
  }

  set value(newVal) {
    if (newVal !== this._value) {
      this._value = isObject(newVal) ? reactive(newVal) : newVal
      // 派发更新
      trigger(this, 'value')
    }
  }
}

function ref(value) {
  if (isRef(value)) return value
  return new RefImpl(value)
}

ref内部通过getter/setter拦截value属性的读写,实现与reactive相同的依赖追踪机制。当传入对象类型时,ref会自动调用reactive将其转为响应式代理。

track与trigger依赖收集派发更新机制

依赖收集和派发更新是响应式系统的核心。Vue3使用WeakMap和Set数据结构建立”目标对象 -> 属性 -> 依赖集合”的三层映射关系:

// 依赖收集与派发更新
const targetMap = new WeakMap() // target -> Map>
let activeEffect = null         // 当前正在执行的effect

function effect(fn) {
  activeEffect = fn
  fn()  // 执行函数,触发依赖收集
  activeEffect = null
}

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()))
  }

  // 将当前effect加入依赖集合
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  const dep = depsMap.get(key)
  if (dep) {
    // 遍历执行所有依赖该属性的effect
    dep.forEach(effect => effect())
  }
}

当组件渲染函数执行时,读取响应式数据触发track,将渲染函数作为effect记录到依赖集合。当数据被修改时,触发trigger,遍历执行所有关联的effect函数,驱动组件重新渲染。

computed与watch的响应式依赖实现

computed基于effect实现,但增加了缓存机制和脏值标记。computed返回的值只在依赖发生变化时重新计算,否则返回缓存结果:

// computed 简化实现
class ComputedRefImpl {
  constructor(getter) {
    this._getter = getter
    this._dirty = true      // 脏值标记
    this._value = undefined
    this.__v_isRef = true
  }

  get value() {
    if (this._dirty) {
      // 在effect上下文中执行getter,自动收集依赖
      this._value = effect(this._getter)
      this._dirty = false
    }
    track(this, 'value')
    return this._value
  }
}

function computed(getter) {
  return new ComputedRefImpl(getter)
}

watch函数通过effect的scheduler选项实现,当依赖变化时不立即执行回调,而是将回调放入调度队列,在nextTick中异步执行:

// watch 简化实现
function watch(source, callback) {
  let oldValue

  const effectFn = effect(() => {
    // 访问source触发依赖收集
    return typeof source === 'function' ? source() : source.value
  }, {
    scheduler(fn) {
      // 依赖变化时,不立即执行,而是调用回调
      const newValue = fn()
      callback(newValue, oldValue)
      oldValue = newValue
    }
  })

  oldValue = effectFn()
}

响应式系统的性能优化策略

Vue3响应式系统在性能方面做了多项优化:懒代理避免初始化时递归遍历深层对象,只在访问时按需代理;缓存代理通过WeakMap避免同一对象被多次代理;批量更新通过microtask队列将多次数据修改合并为一次渲染;标记删除通过markRaw跳过不需要响应式的对象。

实际开发中的性能优化建议:大列表使用shallowRef避免深层代理开销;只读数据使用readonlymarkRaw跳过响应式处理;频繁修改的数据使用computed缓存中间计算结果;避免在模板中直接调用函数产生不必要的依赖收集。

Vue3响应式系统从设计到实现都体现了对性能和开发体验的双重关注。Proxy替代Object.defineProperty解决了深层代理和数组监听的历史痛点,Composition API配合reactive和ref提供了灵活的响应式数据组织方式。理解track/trigger的依赖追踪机制和effect调度策略,有助于在复杂组件中写出更高效的响应式代码。

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

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

相关推荐