Vue3 Composition API响应式原理与reactive/ref依赖追踪实现

Vue3框架的Composition API配合reactive和ref实现了细粒度的响应式更新,其底层依赖ES6 Proxy代理对象完成依赖收集与派发更新的完整链路。前端开发中理解响应式原理,有助于编写高效的组件逻辑和排查渲染性能问题。本文从源码层面拆解Vue3响应式系统的核心机制。

Vue3响应式系统核心:Proxy代理与effect依赖收集

Vue2使用Object.defineProperty劫持对象属性,存在无法监听新增/删除属性、数组索引修改等问题。Vue3改用Proxy代理整个对象,拦截13种操作陷阱(trap),从根本上解决了限制。

响应式系统的核心流程分为三步:

1. reactive()将目标对象包装为Proxy,读写操作被拦截

2. effect()注册副作用函数,在执行过程中访问响应式数据时自动建立依赖关系

3. 数据修改时,Proxy的set陷阱触发,找到对应的effect函数并重新执行

// Vue3响应式核心实现(简化版)
const targetMap = new WeakMap()  // 存储依赖关系
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) {
    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())
  }
}

function reactive(target) {
  const handler = {
    get(obj, key, receiver) {
      const result = Reflect.get(obj, key, receiver)
      track(target, 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) {
        trigger(target, key)     // 写入时触发更新
      }
      return result
    },
    deleteProperty(obj, key) {
      const result = Reflect.deleteProperty(obj, key)
      trigger(target, key)
      return result
    }
  }
  return new Proxy(target, handler)
}

ref与reactive的区别及RefImpl内部实现

reactive只能代理对象类型,number、string等原始值无法被Proxy拦截。ref通过包装对象(RefImpl)解决此问题,内部维护一个value属性,对.value的读写触发track和trigger。

// ref实现原理
function ref(value) {
  if (isRef(value)) return value

  const wrapper = {
    __v_isRef: true,
    _value: convert(value),     // 对象类型调用reactive
    _rawValue: value,

    get value() {
      track(wrapper, 'value')
      return this._value
    },

    set value(newVal) {
      if (newVal !== this._rawValue) {
        this._rawValue = newVal
        this._value = convert(newVal)
        trigger(wrapper, 'value')
      }
    }
  }
  return wrapper
}

function convert(val) {
  return typeof val === 'object' && val !== null
    ? reactive(val)
    : val
}

// 判断是否为ref
function isRef(r) {
  return r && r.__v_isRef === true
}

// 自动解包: template中无需.value
function unref(ref) {
  return isRef(ref) ? ref.value : ref
}

// toRefs: 将reactive对象属性转为ref
function toRefs(object) {
  const result = {}
  for (const key in object) {
    result[key] = toRef(object, key)
  }
  return result
}

function toRef(object, key) {
  return {
    __v_isRef: true,
    get value() {
      return object[key]
    },
    set value(newVal) {
      object[key] = newVal
    }
  }
}

使用toRefs的场景是在Composition API中返回reactive对象时,解构后会丢失响应性。toRefs将每个属性转换为独立的ref,解构后仍保持响应式。

computed计算属性与watch侦听器的依赖追踪

computed本质是一个带缓存的effect,只有当依赖的响应式数据变化时才重新计算。其内部使用lazy标志延迟首次计算,dirty标志标记缓存是否过期。

// computed实现原理
function computed(getter) {
  let value
  let dirty = true

  // 使用effect注册依赖, lazy模式不立即执行
  const runner = effect(getter, {
    lazy: true,
    scheduler: () => {
      // 依赖变化时不立即重新计算
      // 标记为dirty, 下次访问时才计算
      dirty = true
      // 手动触发依赖computed的effect
      trigger(computedRef, 'value')
    }
  })

  const computedRef = {
    __v_isRef: true,
    get value() {
      if (dirty) {
        value = runner()    // 执行getter, 收集依赖
        dirty = false
      }
      track(computedRef, 'value')
      return value
    }
  }
  return computedRef
}

响应式性能优化:shallowReactive与markRaw

深层嵌套对象的递归代理在大型数据结构下存在性能开销。shallowReactive仅代理第一层属性,深层属性保持原始引用:

import { shallowReactive, shallowRef, markRaw } from 'vue'

// shallowReactive: 仅代理第一层
const state = shallowReactive({
  user: { name: '张三', age: 25 },
  list: [1, 2, 3]  // list本身被代理, 但list[0]不会
})

// 修改第一层属性触发更新
state.count = 1     // 触发更新

// 修改深层属性不触发更新
state.user.name = '李四'  // 不触发更新
// 需要替换整个对象
state.user = { name: '李四', age: 25 }  // 触发更新

// markRaw: 标记对象永不转为响应式
const staticData = markRaw({
  largeDataset: Array(100000).fill(0).map((_, i) => i)
})

const state2 = reactive({
  chartData: staticData  // chartData不会被代理
})

// readonly: 创建只读响应式代理
import { readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// copy.count = 1  // 警告: Set operation on key "count" failed

实际项目中,图表大数据、地图实例、三方类库对象(如Three.js Scene、Monaco Editor)不需要响应式追踪,使用markRaw标记可避免不必要的Proxy包装开销。对于跨端小程序开发和Flutter移动端的响应式数据传递,shallowReactive配合手动触发更新是更优的性能方案。

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

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

相关推荐