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/