(最新升级)Vue3入门与项目实战 掌握完整知识体系(已完结)-----97it.夏の哉-----top/--------410/
Vue3 响应式系统揭秘:ref/reactive 原理、依赖收集与更新机制详解
引言
Vue3 的响应式系统是其核心特性之一,它使得数据变化能够自动驱动视图更新。与 Vue2 基于 Object.defineProperty 的实现不同,Vue3 采用了 ES6 的 Proxy 重构了响应式系统,带来了更好的性能和更强大的功能。本文将深入剖析 Vue3 响应式系统的核心实现原理,包括 ref/reactive 的工作原理、依赖收集与触发更新的机制。
一、Vue3 响应式基础
1. reactive 的实现原理
reactive 是 Vue3 中用于创建响应式对象的核心 API,其底层基于 Proxy 实现:
function reactive(target) {
// 如果尝试观察一个只读的代理,则返回只读版本
if (target && target.__v_isReadonly) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
// 如果不是对象,直接返回
if (!isObject(target)) {
return target
}
// 如果目标已经是代理,直接返回
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
return target
}
// 如果已经存在代理,返回已有代理
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只有特定类型的值能被观察
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
Proxy 处理器对象 mutableHandlers 主要实现了以下几个陷阱:
const mutableHandlers = {
get: createGetter(),
set: createSetter(),
deleteProperty,
has,
ownKeys
}
2. ref 的实现原理
ref 是对 reactive 的补充,主要用于处理基本类型的响应式问题:
function ref(value) {
return createRef(value)
}
function createRef(rawValue, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow
this.dep = undefined
this.__v_isRef = true
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this)
}
}
}
ref 的核心是通过类的属性访问器(getter/setter)来实现响应式,对于对象类型的值,内部仍然会使用 reactive 进行转换。
二、依赖收集与触发更新
1. 依赖收集(track)
Vue3 的依赖收集发生在属性访问时,主要通过 track 函数实现:
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
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 = createDep()))
}
trackEffects(dep)
}
function trackEffects(dep) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit
shouldTrack = !wasTracked(dep)
}
} else {
shouldTrack = !dep.has(activeEffect)
}
if (shouldTrack) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
2. 触发更新(trigger)
当响应式数据发生变化时,会通过 trigger 函数触发更新:
function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
let deps = []
if (type === TriggerOpTypes.CLEAR) {
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newValue) {
deps.push(dep)
}
})
} else {
if (key !== void 0) {
deps.push(depsMap.get(key))
}
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
const effects = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
triggerEffects(createDep(effects))
}
function triggerEffects(dep) {
for (const effect of dep) {
if (effect !== activeEffect || effect.allowRecurse) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
三、Effect 与响应式副作用
Vue3 通过 effect 函数来创建响应式副作用,这是响应式系统与组件更新之间的桥梁:
function effect(fn, options) {
if (fn.effect) {
fn = fn.effect.fn
}
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) {
recordEffectScope(_effect, options.scope)
}
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
class ReactiveEffect {
constructor(fn, scheduler = null, scope) {
this.fn = fn
this.scheduler = scheduler
this.active = true
this.deps = []
this.parent = undefined
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
let parent = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
cleanupEffect(this)
}
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
}
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
组件的渲染函数就是在一个 effect 中执行的,当响应式数据变化时,effect 会重新执行,从而触发组件更新。
四、响应式系统的优化
Vue3 的响应式系统相比 Vue2 做了多项优化:
-
Proxy 的优势:
- 可以检测到属性的添加和删除
- 可以检测数组索引的变化和 length 的变化
- 支持 Map、Set、WeakMap、WeakSet 等集合类型
-
嵌套自动解包:
- 访问 ref 值时不需要 .value(在模板中)
- 嵌套的 ref 会自动解包
-
性能优化:
- 依赖收集采用位运算标记,提升性能
- 避免不必要的依赖收集
- 更精确的触发更新,减少不必要的组件更新
-
编译时优化:
- 编译器能够静态分析模板,生成更优化的代码
- PatchFlags 和 dynamicChildren 等优化手段
五、响应式 API 对比
| API | 适用类型 | 深层响应式 | 返回值类型 | 解包需求 |
|---|---|---|---|---|
| reactive | 对象/数组 | 是 | 代理对象 | 无 |
| ref | 任意类型 | 是 | Ref 对象 | 需要 .value |
| shallowRef | 任意类型 | 否 | Ref 对象 | 需要 .value |
| readonly | 对象/数组 | 是 | 只读代理对象 | 无 |
| computed | 计算属性 | - | Ref 对象 | 需要 .value |
六、总结
Vue3 的响应式系统通过 Proxy 和 Reflect 重构,解决了 Vue2 中响应式系统的诸多限制,同时引入了更精细的依赖收集和触发更新机制,配合编译时的优化,使得性能得到了显著提升。理解响应式系统的内部工作原理,有助于我们更好地使用 Vue3 开发应用,并在遇到问题时能够快速定位和解决。
ref 和 reactive 作为两种不同的响应式创建方式,各有适用场景:ref 更适合基本类型和需要保持引用的情况,而 reactive 则适合对象和数组等复杂数据结构。在实际开发中,应根据具体需求选择合适的 API。