Appearance
Vue 3 响应式原理深度解析
Vue 3 的响应式系统是其核心特性之一,相比 Vue 2 基于 Object.defineProperty 的实现,Vue 3 采用了 Proxy 作为底层实现,带来了更强大的功能和更好的性能。本文将深入分析 Vue 3 响应式系统的实现原理。
1. 响应式系统概览
核心概念
typescript
// 响应式系统的核心概念
interface ReactiveEffect {
(): any
deps: Set<Dep>[]
options?: ReactiveEffectOptions
}
type Dep = Set<ReactiveEffect>
interface Target {
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.IS_SHALLOW]?: boolean
[ReactiveFlags.RAW]?: any
}
响应式流程
mermaid
graph TD
A[原始对象] --> B[reactive()]
B --> C[Proxy 代理对象]
C --> D[getter 拦截]
D --> E[依赖收集 track()]
C --> F[setter 拦截]
F --> G[触发更新 trigger()]
G --> H[执行副作用函数]
2. Proxy 基础实现
基本的响应式实现
typescript
// 简化版响应式实现
const targetMap = new WeakMap<any, Map<string | symbol, Set<Function>>>()
let activeEffect: Function | null = null
function track(target: object, key: string | symbol) {
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()))
}
dep.add(activeEffect)
}
function trigger(target: object, key: string | symbol) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
function reactive<T extends object>(target: T): T {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key)
return result
}
})
}
function effect(fn: Function) {
activeEffect = fn
fn()
activeEffect = null
}
使用示例
typescript
// 基本使用
const state = reactive({ count: 0, name: 'Vue' })
effect(() => {
console.log(`Count: ${state.count}`)
})
state.count++ // 输出: Count: 1
3. Vue 3 源码分析
reactive 函数实现
typescript
// packages/reactivity/src/reactive.ts
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// 如果尝试观察只读代理,返回只读版本
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 只能代理对象
if (!isObject(target)) {
return target
}
// 目标已经有相应的 Proxy
if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// 目标已经被代理
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 Handlers 实现
typescript
// packages/reactivity/src/baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
function get(target: Target, key: string | symbol, receiver: object) {
const isReadonlyKeys = key === ReactiveFlags.IS_READONLY
const isShallowKeys = key === ReactiveFlags.IS_SHALLOW
const isReactiveKeys = key === ReactiveFlags.IS_REACTIVE
const isRawKeys = key === ReactiveFlags.RAW
if (isReactiveKeys) {
return !isReadonly
} else if (isReadonlyKeys) {
return isReadonly
} else if (isShallowKeys) {
return isShallow
} else if (isRawKeys && receiver === (isReadonly ? shallowReadonlyMap : reactiveMap).get(target)) {
return target
}
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (isShallow) {
return res
}
if (isRef(res)) {
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
if (!isShallow(target)) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
}
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
4. 依赖收集与触发
track 函数实现
typescript
// packages/reactivity/src/effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
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)
}
}
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
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)
}
}
trigger 函数实现
typescript
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newLength) {
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
}
}
if (deps.length === 1) {
if (deps[0]) {
triggerEffects(deps[0])
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
triggerEffects(createDep(effects))
}
}
5. effect 函数实现
ReactiveEffect 类
typescript
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = 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
if (this.deferStop) {
this.stop()
}
}
}
stop() {
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
effect 函数
typescript
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).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) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
6. ref 实现原理
RefImpl 类
typescript
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
export function ref<T extends object>(
value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
7. computed 实现原理
ComputedRefImpl 类
typescript
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean = false
public _dirty = true
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
const self = toRaw(this)
trackRefValue(self)
if (self._dirty || !self._cacheable) {
self._dirty = false
self._value = self.effect.run()!
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
export function computed<T>(
getter: ComputedGetter<T>,
debugOptions?: DebuggerOptions
): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>,
debugOptions?: DebuggerOptions
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(
getter,
setter,
onlyGetter || !setter,
isSSR
)
return cRef as any
}
8. 性能优化技巧
避免不必要的响应式转换
typescript
// 使用 markRaw 避免深度响应式
const nonReactiveData = markRaw({
largeArray: new Array(10000).fill(0),
complexObject: { /* 复杂对象 */ }
})
// 使用 shallowRef 避免深度监听
const shallowState = shallowRef({
list: [1, 2, 3, 4, 5]
})
// 直接替换整个对象来触发更新
shallowState.value = { list: [1, 2, 3, 4, 5, 6] }
合理使用 computed 缓存
typescript
// 避免在模板中使用复杂计算
// ❌ 不好的做法
<template>
<div>{{ expensiveCalculation(data) }}</div>
</template>
// ✅ 好的做法
const result = computed(() => expensiveCalculation(data.value))
<template>
<div>{{ result }}</div>
</template>
使用 effectScope 管理副作用
typescript
import { effectScope } from 'vue'
function setupFeature() {
const scope = effectScope()
scope.run(() => {
// 在这个作用域内的所有副作用
const state = reactive({ count: 0 })
watchEffect(() => {
console.log(state.count)
})
watch(() => state.count, (count) => {
// 处理计数变化
})
})
// 清理所有副作用
return () => scope.stop()
}
9. 调试技巧
响应式调试
typescript
// 使用 onTrack 和 onTrigger 调试
const state = reactive({ count: 0 })
watchEffect(
() => {
console.log(state.count)
},
{
onTrack(e) {
console.log('tracked:', e)
},
onTrigger(e) {
console.log('triggered:', e)
}
}
)
// 检查对象是否为响应式
console.log(isReactive(state)) // true
console.log(isProxy(state)) // true
console.log(toRaw(state)) // 原始对象
性能分析
typescript
// 分析响应式性能
function analyzeReactivity() {
const start = performance.now()
const state = reactive({
list: new Array(1000).fill(0).map((_, i) => ({ id: i, value: i }))
})
const createTime = performance.now() - start
console.log(`创建响应式对象耗时: ${createTime}ms`)
const accessStart = performance.now()
state.list.forEach(item => item.value)
const accessTime = performance.now() - accessStart
console.log(`访问属性耗时: ${accessTime}ms`)
}
总结
Vue 3 的响应式系统通过 Proxy 实现了更强大和灵活的响应式能力:
- Proxy 优势:可以拦截更多操作,支持数组索引和 length 属性
- 依赖收集:通过 WeakMap 建立目标对象、属性和副作用函数的关系
- 性能优化:使用位运算优化依赖标记,支持嵌套 effect
- 类型安全:完整的 TypeScript 支持
理解响应式原理有助于我们:
- 写出更高效的 Vue 应用
- 避免常见的响应式陷阱
- 进行性能优化和调试
- 深入理解 Vue 3 的设计思想