Vue 3 的响应式系统是其核心功能之一,它基于 JavaScript 的 Proxy API 实现。本文将按照推荐的阅读顺序,逐行解析响应式系统的关键源码。
🧠 响应式系统核心概念
在开始逐行解析之前,我们先了解一下 Vue 3 响应式系统的核心概念:
- 响应式数据: 能够自动追踪变化并触发更新的数据
- 副作用函数 (Effect): 访问响应式数据并可能产生副作用的函数
- 依赖收集: 当副作用函数访问响应式数据时,建立依赖关系
- 触发更新: 当响应式数据变化时,重新执行相关的副作用函数
📁 源码解析顺序
我们将按照以下顺序解析源码:
- ref.ts - Ref 的实现
- effect.ts 和 dep.ts - 响应式系统的核心机制
- reactive.ts - reactive 的实现
- computed.ts - computed 的实现
- watch.ts - watch 的实现
1️⃣ Ref 实现解析 (ref.ts)
我们从最简单的响应式 API 开始,即 ref。
typescript
// ref.ts 文件开头引入相关依赖
import {
type IfAny,
hasChanged,
isArray,
isFunction,
isObject,
} from '@vue/shared'
首先引入了来自 @vue/shared 的工具函数,这些函数用于类型检查和值比较。
typescript
import { Dep, getDepFromReactive } from './dep'
引入了 Dep 类,它是依赖管理的核心类,用于收集和通知依赖。
typescript
import {
type Builtin,
type ShallowReactiveMarker,
isProxy,
isReactive,
isReadonly,
isShallow,
toRaw,
toReactive,
} from './reactive'
从 reactive.ts 引入了响应式相关的工具函数和类型。
typescript
import type { ComputedRef, WritableComputedRef } from './computed'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
import { warn } from './warning'
引入了计算属性相关类型和常量定义。
typescript
declare const RefSymbol: unique symbol
export declare const RawSymbol: unique symbol
export interface Ref<T = any, S = T> {
get value(): T
set value(_: S)
/**
* Type differentiator only.
* We need this to be in public dts but don't want it to show up in IDE
* autocomplete, so we use a private Symbol instead.
*/
[RefSymbol]: true
}
定义了 Ref 接口,它包含一个 value 属性的 getter 和 setter。[RefSymbol] 是一个类型区分符,用于在类型系统中识别 Ref 对象。
typescript
/**
* Checks if a value is a ref object.
*
* @param r - The value to inspect.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isref}
*/
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return r ? r[ReactiveFlags.IS_REF] === true : false
}
isRef 函数用于检查一个值是否为 Ref 对象,通过检查 [ReactiveFlags.IS_REF]
属性来判断。
typescript
/**
* Takes an inner value and returns a reactive and mutable ref object, which
* has a single property `.value` that points to the inner value.
*
* @param value - The object to wrap in the ref.
* @see {@link https://vuejs.org/api/reactivity-core.html#ref}
*/
export function ref<T>(
value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
ref 函数创建一个响应式的 ref 对象。它有两种重载形式:
- 接收一个值并返回对应的 Ref 对象
- 不接收参数,返回一个 undefined 类型的 Ref 对象
实际实现委托给了 createRef 函数。
typescript
declare const ShallowRefMarker: unique symbol
export type ShallowRef<T = any, S = T> = Ref<T, S> & {
[ShallowRefMarker]?: true
}
定义了 ShallowRef 类型,它是一个浅层的 Ref,只追踪 .value
的变化,不追踪值内部的变化。
typescript
/**
* Shallow version of {@link ref}.
*
* @example
* ```js
* const state = shallowRef({ count: 1 })
*
* // does NOT trigger change
* state.value.count = 2
*
* // does trigger change
* state.value = { count: 2 }
* ```
*
* @param value - The "inner value" for the shallow ref.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
*/
export function shallowRef<T>(
value: T,
): Ref extends T
? T extends Ref
? IfAny<T, ShallowRef<T>, T>
: ShallowRef<T>
: ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
shallowRef 创建一个浅层的响应式引用,只追踪 .value
的变化,而不追踪值内部的变化。
typescript
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
createRef 是创建 Ref 的内部函数,如果传入的值已经是 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)
}
}
}
RefImpl 是 Ref 的具体实现类:
- _value: 存储当前值
_rawValue
: 存储原始值(未被 Proxy 包装)- dep: 依赖管理对象
- __v_isRef: 标识这是一个 Ref 对象
__v_isShallow
: 标识是否为浅层响应式
构造函数中,根据是否为浅层响应式来决定如何处理传入的值。
getter 中调用 trackRefValue
进行依赖收集。
setter 中:
- 首先判断是否应该直接使用新值
- 将新值转换为原始值
- 比较新旧值,如果发生变化则更新值并调用
triggerRefValue
触发更新
typescript
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
const dep = ref.dep
if (dep) {
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: ref,
key: 'value',
newValue: newVal,
})
} else {
dep.notify()
}
}
}
triggerRefValue
函数用于触发 Ref 的更新,通知所有依赖这个 Ref 的副作用函数重新执行。
typescript
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
if (__DEV__) {
trackEffects(
ref.dep || (ref.dep = createDep()),
{
target: ref,
type: TrackOpTypes.GET,
key: 'value',
},
)
} else {
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
}
trackRefValue
函数用于收集对 Ref 的依赖,当 Ref 的值被访问时调用。
2️⃣ Effect 和 Dep 实现解析
Effect 实现 (effect.ts)
typescript
export interface ReactiveEffectOptions extends DebuggerOptions {
lazy?: boolean
scheduler?: EffectScheduler
allowRecurse?: boolean
onStop?: () => void
}
定义了 ReactiveEffectOptions 接口,用于配置副作用函数的行为。
typescript
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T>
/**
* @internal
*/
allowRecurse?: boolean
/**
* @internal
*/
private deferStop?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
/**
* @internal
*/
_dirtyLevel = DirtyLevels.Dirty
/**
* @internal
*/
_trackId = 0
/**
* @internal
*/
_runnings = 0
/**
* @internal
*/
_shouldSchedule = false
/**
* @internal
*/
_depsLength = 0
constructor(
public fn: () => T,
public trigger: () => void,
public scheduler?: EffectScheduler,
scope?: EffectScope,
) {
recordEffectScope(this, scope)
}
}
ReactiveEffect
类是副作用函数的核心实现:
active
: 标识副作用函数是否处于活跃状态deps
: 存储该副作用函数依赖的所有 Dep 对象parent
: 指向父级副作用函数(处理嵌套情况)fn
: 实际的副作用函数scheduler
: 调度器,控制副作用函数的执行时机- 其他内部属性用于优化和调试
typescript
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions,
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner)._isEffect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
const _effect = new ReactiveEffect(fn, NOOP, () => {
if (_effect.dirty) {
_effect.run()
}
})
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
}
effect
函数用于创建一个副作用函数:
- 如果传入的函数已经是副作用函数,则获取其原始函数
- 创建
ReactiveEffect
实例 - 应用配置选项
- 如果不是懒执行,则立即运行一次
- 返回一个绑定到
_effect.run
的运行器函数
typescript
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent && lastShouldTrack) {
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()
}
}
}
run
方法是执行副作用函数的核心逻辑:
- 检查副作用函数是否处于活跃状态
- 设置父子关系,防止循环调用
- 设置全局状态(
activeEffect
、shouldTrack
) - 初始化依赖标记
- 执行副作用函数
- 清理和恢复状态
Dep 实现 (dep.ts)
typescript
export class Dep {
activeLink?: Link = undefined
subs?: Link = undefined
subsHead?: Link
map?: KeyToDepMap = undefined
key?: unknown = undefined
sc: number = 0
constructor(public computed?: ComputedRefImpl | undefined) {
if (__DEV__) {
this.subsHead = undefined
}
}
}
Dep
类用于管理依赖关系:
activeLink
: 指向当前活跃的依赖链接subs
: 指向订阅者链表的尾部subsHead
: 指向订阅者链表的头部(开发模式下使用)map
和key
: 用于对象属性的依赖清理sc
: 订阅者计数器
typescript
track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
if (!activeEffect || !shouldTrack || activeEffect === this.computed) {
return
}
let link = this.activeLink
if (link === undefined || link.sub !== activeEffect) {
link = this.activeLink = new Link(activeEffect, this)
if (!activeEffect.deps) {
activeEffect.deps = activeEffect.depsTail = link
} else {
link.prevDep = activeEffect.depsTail
activeEffect.depsTail!.nextDep = link
activeEffect.depsTail = link
}
if (__DEV__ && debugInfo) {
link.debugInfo = debugInfo
}
}
return link
}
track 方法用于收集依赖:
- 检查是否应该收集依赖
- 如果当前副作用函数尚未订阅此 Dep,则创建新的链接
- 将链接添加到副作用函数的依赖链表中
- 返回链接对象
typescript
trigger(debugInfo?: DebuggerEventExtraInfo): void {
let link = this.subs
while (link) {
const { sub } = link
if (sub.notify(debugInfo)) {
const currentEffect = activeSub
activeSub = sub
try {
sub.trigger()
} finally {
activeSub = currentEffect
}
}
link = link.prevSub
}
}
trigger 方法用于触发更新:
3️⃣ Reactive 实现解析 (reactive.ts)
typescript
export function reactive<T extends object>(target: T): Reactive<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
reactive
函数用于创建响应式对象:
- 如果目标对象已经是只读的,则直接返回
- 调用
createReactiveObject
创建响应式对象
typescript
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (__DEV__) {
warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
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
}
createReactiveObject 是创建响应式对象的核心函数:
- 检查目标是否为对象,如果不是则直接返回
- 检查目标是否已经是 Proxy,如果是则直接返回
- 检查是否已存在对应的 Proxy,如果存在则返回已存在的
- 检查目标类型是否可以被观察
- 根据目标类型选择合适的处理器创建 Proxy
- 将 Proxy 存入映射表并返回
4️⃣ Computed 实现解析 (computed.ts)
typescript
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false,
): ComputedRef<T> {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T> | undefined
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = __DEV__
? () => {
warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(getter, setter, isSSR)
if (__DEV__ && debugOptions && !isSSR) {
cRef.onTrack = debugOptions.onTrack
cRef.onTrigger = debugOptions.onTrigger
}
return cRef as any
}
computed 函数用于创建计算属性:
- 解析 getter 和 setter
- 对于只读计算属性,设置一个警告的 setter
- 创建 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 __v_isReadonly: boolean = false
public _dirty = true
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
public readonly _setter: ComputedSetter<T>,
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.__v_isReadonly = isFunction(getter)
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
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)
}
}
ComputedRefImpl
是计算属性的具体实现:
dep
: 依赖管理对象_value
: 缓存的计算结果effect
: 用于计算值的副作用函数_dirty
: 标识计算结果是否需要重新计算_cacheable
: 标识是否可以缓存结果
getter 中:
- 进行依赖收集
- 如果值是"脏"的或者不可缓存,则重新计算
- 返回缓存的值
setter 中直接调用 _setter
函数
5️⃣ Watch 实现解析 (watch.ts)
typescript
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
options?: WatchOptions<Immediate>,
): WatchStopHandle {
if (__DEV__ && !isFunction(cb)) {
warn(
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
`supports \`watch(source, cb, options?) signature.`,
)
}
return doWatch(source as any, cb, options)
}
watch 函数用于创建侦听器:
- 检查回调函数是否为函数类型
- 委托给 doWatch 函数执行实际逻辑
typescript
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ,
): WatchStopHandle {
if (__DEV__ && !cb) {
// ...
}
const warnInvalidSource = (s: unknown) => {
warn(
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, ` +
`a reactive object, or an array of these types.`,
)
}
const instance = currentInstance
const call = (fn: Function, type: WatchErrorCodes, args?: unknown[]) =>
invokeWithErrorHandling(fn, null, args, instance, type)
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => source
deep = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s))
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return traverse(s)
} else if (isFunction(s)) {
return call(s, WatchErrorCodes.WATCH_GETTER)
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () =>
call(source, WatchErrorCodes.WATCH_GETTER)
} else {
// no cb -> simple effect
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return call(source, WatchErrorCodes.WATCH_CALLBACK, [onCleanup])
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
let cleanup: (() => void) | undefined
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
call(fn, WatchErrorCodes.WATCH_CLEANUP)
}
}
let oldValue: any = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
if (!effect.active) {
return
}
if (cb) {
// watch(source, cb)
const newValue = effect.run()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as any[])[i]),
)
: hasChanged(newValue, oldValue))
) {
// cleanup before running cb again
if (cleanup) {
cleanup()
}
call(cb, WatchErrorCodes.WATCH_CALLBACK, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup,
])
oldValue = newValue
}
} else {
// watchEffect
effect.run()
}
}
// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// 'pre'
job.pre = true
if (instance) job.id = instance.uid
scheduler = () => queueJob(job)
}
const effect = new ReactiveEffect(getter, NOOP, scheduler)
const update = effect.run.bind(effect)
update()
const unwatch = () => {
if (effect.active) {
cleanupEffect(effect)
if (cleanup) {
cleanup()
}
}
}
return unwatch
}
doWatch
函数是 watch 的核心实现:
- 根据不同类型的数据源创建对应的 getter 函数
- 处理 deep 侦听的情况
- 创建清理函数机制
- 创建调度器,控制回调函数的执行时机
- 创建副作用函数并立即执行一次
- 返回停止侦听的函数
📊 总结
Vue 3 的响应式系统是一个复杂而精妙的设计,通过以下几个核心概念协同工作:
- Ref : 通过 getter/setter 拦截对
.value
的访问和修改 - Reactive: 通过 Proxy 拦截对对象属性的访问和修改
- Effect: 包装副作用函数,建立数据和副作用之间的联系
- Dep: 管理依赖关系,当数据变化时通知相关的副作用函数
- Computed: 基于其他响应式数据计算得出,并具有缓存机制
- Watch: 侦听响应式数据的变化并执行回调函数
这套系统通过精确的依赖收集和高效的更新触发机制,实现了精准的响应式更新,避免了不必要的计算和渲染,从而提升了应用性能。
通过逐行解析这些核心源码,我们可以更深入地理解 Vue 3 响应式系统的实现原理,这对于我们在实际开发中更好地使用 Vue 以及进行性能优化都有很大帮助。
如有不足之处,敬请指正
参考资料:
- Vue 3 官方文档
- Vue 3 源码: github.com/vuejs/core