Vue3 Composition API响应式系统与自定义Ref实现原理

Vue3的响应式系统基于ES6 Proxy实现,相比Vue2的Object.defineProperty方案有本质提升。Vue3生态中,Proxy可以拦截对象的所有属性访问和修改操作,包括数组索引修改和length变化,而Object.defineProperty只能拦截已定义的属性,数组方法修改和新增属性无法自动响应。前端工程化中,理解响应式底层原理对构建高性能组件库至关重要。

Proxy代理机制与响应式核心实现

Proxy拦截13种操作(trap),Vue3主要使用get和set两个trap。get trap在属性读取时进行依赖收集,set trap在属性修改时触发更新。整个响应式系统的核心流程:读取属性时收集当前effect到依赖映射表,修改属性时从映射表取出对应effect并执行。

// Proxy响应式基础实现
const reactiveMap = new WeakMap()

function reactive(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) {
        trigger(obj, key)  // 触发更新
      }
      return result
    }
  })
  reactiveMap.set(target, proxy)
  return proxy
}

Reflect的使用保证this指向正确。receiver参数确保原型链上的getter/setter能正确访问原始对象。深层响应通过在get trap中递归调用reactive实现,首次访问时才创建嵌套代理,避免初始化时遍历整个对象树的性能开销。

reactive与ref实现原理对比分析

reactive用于代理对象类型数据,ref用于包装基本类型。ref内部通过对象包装实现,value属性访问时触发依赖收集。Vue3中ref的实现比reactive简单,因为基本类型无法直接被Proxy代理,需要通过对象属性访问间接实现响应式。

// ref实现原理
function ref(value) {
  return new RefImpl(value)
}

class RefImpl {
  constructor(value) {
    this._value = toReactive(value)
    this._rawValue = value
    this.__v_isRef = true
    this.dep = new Dep()  // 每个ref独立的依赖集合
  }

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

  set value(newVal) {
    if (newVal !== this._rawValue) {
      this._rawValue = newVal
      this._value = toReactive(newVal)
      this.dep.trigger()  // 触发更新
    }
  }
}

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

ref的dep是独立的Dep实例,而reactive的依赖存储在全局的targetMap中,以对象和属性为键。这种设计差异源于基本类型只有一个value接口,无需按属性粒度收集依赖。ref包装对象类型时,内部调用toReactive自动转换为reactive代理,实现ref和reactive的混用。

effect依赖收集与触发更新机制

effect是Vue3响应式系统的调度单元。组件渲染、watch、computed都基于effect实现。依赖收集的核心数据结构是targetMap:WeakMap<object, Map<key, Set<effect>>>,三层嵌套映射建立”对象-属性-effect”的依赖关系。effect执行时,内部访问的响应式属性自动收集当前effect作为依赖。

const targetMap = new WeakMap()
let activeEffect = null

class ReactiveEffect {
  constructor(fn, scheduler) {
    this.fn = fn
    this.scheduler = scheduler  // 自定义调度器
    this.deps = []              // 反向引用
  }

  run() {
    const parent = activeEffect
    activeEffect = this
    try {
      return this.fn()
    } finally {
      activeEffect = parent
    }
  }

  stop() {
    this.deps.forEach(dep => dep.delete(this))
    this.deps.length = 0
  }
}

function effect(fn, options = {}) {
  const _effect = new ReactiveEffect(fn, options.scheduler)
  _effect.run()
  const runner = _effect.run.bind(_effect)
  runner.effect = _effect
  return runner
}

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()))
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (!dep) return
  const effects = [...dep]
  effects.forEach(effect => {
    if (effect !== activeEffect) {
      if (effect.scheduler) effect.scheduler()
      else effect.run()
    }
  })
}

scheduler机制是异步批量更新的基础。Vue3组件更新使用scheduler将渲染effect推入微任务队列,在同一tick内合并多次状态修改为一次渲染。这避免了频繁修改状态导致的重复渲染性能问题。watchEffect和watch也通过scheduler实现异步回调调度。

自定义ref与computed高级实现

customRef允许开发者控制依赖收集和触发的时机,实现防抖、节流、懒加载等高级场景。computed基于effect实现,通过dirty标志和缓存机制避免重复计算。响应式布局和表单联动场景中,自定义ref是构建复杂响应式逻辑的核心工具。

// 自定义ref:防抖搜索
import { customRef, computed } from 'vue'

function debouncedRef(value, delay = 200) {
  let timer = null
  return customRef((track, trigger) => ({
    get() {
      track()  // 依赖收集
      return value
    },
    set(newVal) {
      clearTimeout(timer)
      timer = setTimeout(() => {
        value = newVal
        trigger()  // 延迟触发更新
      }, delay)
    }
  }))
}

const searchKeyword = debouncedRef('', 300)
// 用户输入时防抖,300ms内连续输入只触发一次更新

// computed懒计算实现
class ComputedRefImpl {
  constructor(getter, setter) {
    this._setter = setter
    this._dirty = true
    this._value = undefined
    this.__v_isRef = true

    this._effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        this.dep?.trigger()
      }
    })
  }

  get value() {
    this.dep?.track()
    if (this._dirty) {
      this._dirty = false
      this._value = this._effect.run()
    }
    return this._value
  }

  set value(newVal) {
    this._setter(newVal)
  }
}

// 使用示例
const firstName = ref('Zhang')
const lastName = ref('San')
const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (val) => {
    [firstName.value, lastName.value] = val.split(' ')
  }
})

computed的懒计算特性通过_dirty标志实现:首次访问时执行getter并缓存结果,后续访问直接返回缓存值。当getter内部依赖的响应式数据变化时,scheduler将_dirty设为true并触发依赖computed的effect重新执行,下次访问时重新计算。这种设计避免了无用的计算开销,只有被使用的computed才会执行。shallowRef和shallowReactive是浅响应变体,只对第一层属性响应式,适用于大对象只关心顶层变化的场景,减少不必要的深层代理开销。

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

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

相关推荐