源码深度解读
2026-03-08 | Vue 3 Reactivity System
第一部分:基础概念
第二部分:核心概念
第三部分:核心实现
第四部分:进阶
Vue 3 的响应式系统是基于 ES6 Proxy 实现的全新响应式方案,相比 Vue 2 的 Object.defineProperty 方案有质的飞跃。
核心特性
主要优势
┌─────────────────────────────────────────────────────────┐
│ @vue/reactivity │
├─────────────────────────────────────────────────────────┤
│ reactive() │ ref() │ computed() │ watch() │
├─────────────────────────────────────────────────────────┤
│ Dep │ Link │ ReactiveEffect │ EffectScope │
├─────────────────────────────────────────────────────────┤
│ Proxy Handlers: baseHandlers / collectionHandlers │
├─────────────────────────────────────────────────────────┤
│ targetMap: WeakMap<object, Map<key, Dep>> │
└─────────────────────────────────────────────────────────┘
响应式系统采用分层架构:API 层 → Effect 层 → Dep 层 → Proxy 层
| 层级 | 职责 | 核心模块 |
|---|---|---|
| API 层 | 用户接口 | reactive, ref, computed, watch |
| Effect 层 | 副作用管理 | ReactiveEffect, EffectScope |
| Dep 层 | 依赖追踪 | Dep, Link, targetMap |
| Proxy 层 | 拦截读写 | baseHandlers, collectionHandlers |
Vue 0.x - 1.x:Object.defineProperty + Object.observe(已废弃)
Vue 2.x:Object.defineProperty 递归遍历,需要 $set/$delete
Vue 3.x:ES6 Proxy + WeakMap,完整响应式支持
关键突破:Proxy 可以拦截对象的所有操作,包括属性添加/删除、数组索引变化等
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 实现方式 | Object.defineProperty | Proxy |
| 数组支持 | 需要重写方法 | 原生支持 |
| Map/Set | ❌ 不支持 | ✅ 完全支持 |
| 属性添加 | 需要 Vue.set | 自动响应 |
| 性能 | 初始化时全量遍历 | 惰性代理 |
响应式原语
计算与侦听
// packages/reactivity/src/reactive.ts
export function reactive<T extends object>(target: T): Reactive<T> {
// 如果已经是 readonly,直接返回
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers, // 对象/数组 handler
mutableCollectionHandlers, // Map/Set handler
reactiveMap, // 缓存 WeakMap
)
}
reactive() 返回原始对象的 Proxy 代理,所有属性访问都会被拦截
// packages/reactivity/src/ref.ts
class RefImpl<T = any> {
private _value: T
private _rawValue: T
dep: Dep = new Dep()
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false
constructor(value: T, isShallow: boolean) {
this._rawValue = isShallow ? value : toRaw(value)
this._value = isShallow ? value : toReactive(value)
this[ReactiveFlags.IS_SHALLOW] = isShallow
}
get value() {
this.dep.track() // 依赖收集
return this._value
}
set value(newValue) {
if (hasChanged(newValue, this._rawValue)) {
this._rawValue = newValue
this._value = toReactive(newValue)
this.dep.trigger() // 触发更新
}
}
}
// packages/reactivity/src/computed.ts
export class ComputedRefImpl<T = any> implements Subscriber {
_value: any = undefined
readonly dep: Dep = new Dep(this)
deps?: Link = undefined // 计算属性也有自己的依赖
depsTail?: Link = undefined
flags: EffectFlags = EffectFlags.DIRTY
globalVersion: number = globalVersion - 1
constructor(
public fn: ComputedGetter<T>,
private readonly setter: ComputedSetter<T> | undefined,
isSSR: boolean,
) {}
get value(): T {
this.dep.track() // 收集读取者
refreshComputed(this) // 惰性求值
return this._value
}
notify(): true | void {
this.flags |= EffectFlags.DIRTY // 标记为脏
if (!(this.flags & EffectFlags.NOTIFIED)) {
batch(this, true) // 批量通知
return true
}
}
}
核心思想:当响应式数据被读取时,记录当前活跃的副作用;当数据变化时,重新执行这些副作用。
// 全局状态
export let activeSub: Subscriber | undefined // 当前活跃的副作用
export let shouldTrack = true // 是否启用追踪
// 依赖关系存储
export const targetMap: WeakMap<object, Map<any, Dep>> = new WeakMap()
// 结构: target → key → Dep → Subscribers
关键点:使用 WeakMap 存储依赖关系,对象被垃圾回收时自动清理
┌──────────────────────────────────────────────────────────┐
│ targetMap (WeakMap) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ target (object) → depsMap (Map) │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ key → Dep │ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ subs (Link 链表)│ │ │ │
│ │ │ │ ↓ │ │ │ │
│ │ │ │ Subscriber 1 │ │ │ │
│ │ │ │ Subscriber 2 │ │ │ │
│ │ │ │ ... │ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
// packages/reactivity/src/reactive.ts
export interface Target {
[ReactiveFlags.SKIP]?: boolean // 跳过响应式转换
[ReactiveFlags.IS_REACTIVE]?: boolean // 是否是响应式
[ReactiveFlags.IS_READONLY]?: boolean // 是否只读
[ReactiveFlags.IS_SHALLOW]?: boolean // 是否浅层
[ReactiveFlags.RAW]?: any // 原始对象
}
// 目标类型枚举
enum TargetType {
INVALID = 0, // 不可观察
COMMON = 1, // 普通对象/数组
COLLECTION = 2, // Map/Set/WeakMap/WeakSet
}
Target 接口定义了响应式对象的内部标记,用于识别和处理不同类型的对象
// packages/reactivity/src/constants.ts
export enum ReactiveFlags {
SKIP = '__v_skip', // 跳过响应式
IS_REACTIVE = '__v_isReactive', // 响应式标记
IS_READONLY = '__v_isReadonly', // 只读标记
IS_SHALLOW = '__v_isShallow', // 浅层标记
RAW = '__v_raw', // 原始对象引用
IS_REF = '__v_isRef', // Ref 标记
}
使用场景
注意事项
// packages/reactivity/src/dep.ts
export class Dep {
version = 0 // 版本号,用于快速判断变化
activeLink?: Link = undefined // 当前活跃的链接
subs?: Link = undefined // 订阅者链表(尾部)
subsHead?: Link // 订阅者链表(头部,DEV only)
map?: KeyToDepMap // 所属的 depsMap
key?: unknown // 对应的属性键
sc: number = 0 // 订阅者计数
readonly __v_skip = true // 跳过响应式转换
constructor(public computed?: ComputedRefImpl | undefined) {}
track(): Link | undefined { // 依赖收集
if (!activeSub || !shouldTrack) return
// 创建或复用 Link 连接 Dep 和 Subscriber
}
trigger(): void { // 触发更新
this.version++
globalVersion++
this.notify()
}
notify(): void { // 通知所有订阅者
startBatch()
for (let link = this.subs; link; link = link.prevSub) {
if (link.sub.notify()) { // computed 返回 true
(link.sub as ComputedRefImpl).dep.notify()
}
}
endBatch()
}
}
// packages/reactivity/src/dep.ts
export class Link {
version: number // 版本号,用于清理
// Dep → Sub 链表指针
nextSub?: Link // 下一个订阅者
prevSub?: Link // 上一个订阅者
// Sub → Dep 链表指针
nextDep?: Link // 下一个依赖
prevDep?: Link // 上一个依赖
prevActiveLink?: Link // 保存之前的活跃链接
constructor(
public sub: Subscriber, // 订阅者
public dep: Dep, // 依赖
) {
this.version = dep.version
}
}
设计亮点:Link 同时属于两个双向链表 - 一个用于 Dep 管理订阅者,一个用于 Subscriber 管理依赖。这使得依赖清理非常高效。
// packages/reactivity/src/effect.ts
export class ReactiveEffect<T = any> implements Subscriber {
deps?: Link = undefined // 依赖链表头
depsTail?: Link = undefined // 依赖链表尾
flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
next?: Subscriber = undefined // 批处理链
cleanup?: () => void // 清理函数
scheduler?: EffectScheduler // 调度器
constructor(public fn: () => T) {}
run(): T {
if (!(this.flags & EffectFlags.ACTIVE)) {
return this.fn()
}
this.flags |= EffectFlags.RUNNING
cleanupEffect(this)
prepareDeps(this) // 准备依赖(标记 -1)
const prevEffect = activeSub
activeSub = this // 设置为当前活跃 effect
try {
return this.fn() // 执行函数,触发依赖收集
} finally {
cleanupDeps(this) // 清理未使用的依赖
activeSub = prevEffect
this.flags &= ~EffectFlags.RUNNING
}
}
notify(): void {
if (!(this.flags & EffectFlags.NOTIFIED)) {
batch(this) // 加入批处理队列
}
}
}
// packages/reactivity/src/effect.ts
export enum EffectFlags {
ACTIVE = 1 << 0, // 效果是否活跃
RUNNING = 1 << 1, // 是否正在执行
TRACKING = 1 << 2, // 是否启用追踪
NOTIFIED = 1 << 3, // 是否已加入批处理队列
DIRTY = 1 << 4, // computed 是否需要重新计算
ALLOW_RECURSE = 1 << 5, // 是否允许递归
PAUSED = 1 << 6, // 是否暂停
EVALUATED = 1 << 7, // computed 是否已求值
}
| 标志 | 用途 |
|---|---|
| ACTIVE | effect.stop() 后清除,停止追踪 |
| DIRTY | computed 依赖变化时设置,表示需要重新计算 |
| NOTIFIED | 防止重复加入批处理队列 |
// packages/reactivity/src/effect.ts
export interface Subscriber extends DebuggerOptions {
deps?: Link // 依赖链表头
depsTail?: Link // 依赖链表尾
flags: EffectFlags // 状态标志
next?: Subscriber // 批处理链
notify(): true | void // 通知方法
// computed 返回 true 表示需要继续通知其订阅者
}
实现类:ReactiveEffect 和 ComputedRefImpl 都实现了 Subscriber 接口,这使得 computed 可以作为其他 computed 或 effect 的依赖。
class RefImpl<T = any> {
_value: T // 包装后的值
private _rawValue: T // 原始值
dep: Dep = new Dep() // 独立的 Dep
// 标记
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false
get value() {
// 开发模式下会记录调试信息
if (__DEV__) {
this.dep.track({
target: this,
type: TrackOpTypes.GET,
key: 'value',
})
} else {
this.dep.track()
}
return this._value
}
set value(newValue) {
const oldValue = this._rawValue
const useDirectValue =
this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue)
if (hasChanged(newValue, oldValue)) {
this._rawValue = newValue
this._value = useDirectValue ? newValue : toReactive(newValue)
// 触发更新
this.dep.trigger()
}
}
}
export class ComputedRefImpl<T = any> implements Subscriber {
_value: any = undefined
readonly dep: Dep = new Dep(this) // 自己是 computed 的 dep
// Computed 也是 Subscriber,有自己的依赖
deps?: Link = undefined
depsTail?: Link = undefined
flags: EffectFlags = EffectFlags.DIRTY
globalVersion: number = globalVersion - 1
isSSR: boolean
next?: Subscriber = undefined
constructor(
public fn: ComputedGetter<T>,
private readonly setter: ComputedRefImpl<T> | undefined,
isSSR: boolean,
) {}
notify(): true | void {
this.flags |= EffectFlags.DIRTY
if (!(this.flags & EffectFlags.NOTIFIED) && activeSub !== this) {
batch(this, true) // isComputed = true
return true // 返回 true 表示需要继续传播
}
}
get value(): T {
const link = this.dep.track()
refreshComputed(this) // 惰性求值
if (link) {
link.version = this.dep.version
}
return this._value
}
}
// packages/reactivity/src/effectScope.ts
export class EffectScope {
private _active = true
private _on = 0
effects: ReactiveEffect[] = [] // 收集的 effects
cleanups: (() => void)[] = [] // 清理函数
private _isPaused = false
parent: EffectScope | undefined // 父作用域
scopes: EffectScope[] | undefined // 子作用域
private index: number | undefined // 在父作用域中的索引
constructor(public detached = false) {
this.parent = activeEffectScope
if (!detached && activeEffectScope) {
this.index = (activeEffectScope.scopes ||
(activeEffectScope.scopes = [])).push(this) - 1
}
}
run<T>(fn: () => T): T | undefined {
if (this._active) {
const currentEffectScope = activeEffectScope
try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = currentEffectScope
}
}
}
stop(fromParent?: boolean): void {
if (this._active) {
// 停止所有 effects
for (let i = 0; i < this.effects.length; i++) {
this.effects[i].stop()
}
// 执行清理函数
for (let i = 0; i < this.cleanups.length; i++) {
this.cleanups[i]()
}
// 停止子作用域
if (this.scopes) {
for (let i = 0; i < this.scopes.length; i++) {
this.scopes[i].stop(true)
}
}
}
}
}
// packages/reactivity/src/baseHandlers.ts
// 基类
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object): any {
// 处理 ReactiveFlags
// 数组方法拦截
// track 依赖收集
// ref 解包
// 深层响应式转换
}
}
// 可变 handler
class MutableReactiveHandler extends BaseReactiveHandler {
set(target, key, value, receiver): boolean {
// toRaw 处理
// 触发 trigger
}
deleteProperty(target, key): boolean { ... }
has(target, key): boolean { ... }
ownKeys(target): (string | symbol)[] { ... }
}
// 只读 handler
class ReadonlyReactiveHandler extends BaseReactiveHandler {
set(target, key) { warn('readonly'); return true }
deleteProperty(target, key) { warn('readonly'); return true }
}
// packages/reactivity/src/reactive.ts
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// 1. 非对象直接返回
if (!isObject(target)) {
if (__DEV__) {
warn(`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}`)
}
return target
}
// 2. 已经是 Proxy,直接返回(除了 readonly(reactive()))
if (target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// 3. 检查目标类型
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 4. 检查缓存
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 5. 创建 Proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
// 响应式
export function reactive<T extends object>(target: T): Reactive<T> {
if (isReadonly(target)) return target
return createReactiveObject(
target, false,
mutableHandlers, mutableCollectionHandlers, reactiveMap,
)
}
// 浅层响应式
export function shallowReactive<T extends object>(target: T): ShallowReactive<T> {
return createReactiveObject(
target, false,
shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap,
)
}
// 只读
export function readonly<T extends object>(target: T): DeepReadonly<...> {
return createReactiveObject(
target, true,
readonlyHandlers, readonlyCollectionHandlers, readonlyMap,
)
}
// 浅层只读
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
return createReactiveObject(
target, true,
shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap,
)
}
// packages/reactivity/src/ref.ts
// 创建 ref
export function ref<T>(value: T): Ref<UnwrapRef<T>> {
return createRef(value, false)
}
// 创建 shallow ref
export function shallowRef<T>(value: T): ShallowRef<T> {
return createRef(value, true)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
// 触发 shallow ref 更新
export function triggerRef(ref: Ref): void {
if ((ref as RefImpl).dep) {
(ref as RefImpl).dep.trigger()
}
}
// 解包 ref
export function unref<T>(ref: MaybeRef<T>): T {
return isRef(ref) ? ref.value : ref
}
// 转换为 ref
export function toRef<T>(value: T): Ref<T> {
if (isRef(value)) return value
if (isFunction(value)) return new GetterRefImpl(value)
if (isObject(value) && arguments.length > 1) {
return new ObjectRefImpl(value, key!, defaultValue)
}
return ref(value)
}
// packages/reactivity/src/dep.ts
export function track(target: object, type: TrackOpTypes, key: unknown): void {
if (shouldTrack && activeSub) {
// 1. 获取 target 对应的 depsMap
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 2. 获取 key 对应的 Dep
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Dep()))
dep.map = depsMap
dep.key = key
}
// 3. 执行依赖追踪
if (__DEV__) {
dep.track({ target, type, key })
} else {
dep.track()
}
}
}
track 在 Proxy 的 get 拦截器中被调用,建立 target.key → activeSub 的依赖关系
// packages/reactivity/src/dep.ts
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
): void {
const depsMap = targetMap.get(target)
if (!depsMap) {
globalVersion++
return
}
const run = (dep: Dep | undefined) => {
if (dep) dep.trigger()
}
startBatch()
if (type === TriggerOpTypes.CLEAR) {
// 清空集合
depsMap.forEach(run)
} else if (key !== void 0) {
// 触发特定 key 的依赖
run(depsMap.get(key))
// 数组长度变化
if (isArray(target) && isIntegerKey(key)) {
run(depsMap.get('length'))
run(depsMap.get(ARRAY_ITERATE_KEY))
}
// 添加/删除时触发迭代
if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
run(depsMap.get(ITERATE_KEY))
}
}
endBatch()
}
// packages/reactivity/src/effect.ts
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions,
): ReactiveEffectRunner<T> {
// 如果 fn 已经是 effect runner,提取原始函数
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 创建 effect
const e = new ReactiveEffect(fn)
// 应用选项
if (options) {
extend(e, options)
}
// 首次执行
try {
e.run()
} catch (err) {
e.stop()
throw err
}
// 返回 runner
const runner = e.run.bind(e) as ReactiveEffectRunner
runner.effect = e
return runner
}
// 停止 effect
export function stop(runner: ReactiveEffectRunner): void {
runner.effect.stop()
}
// packages/reactivity/src/computed.ts
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false,
): ComputedRef<T> | WritableComputedRef<T> {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T> | undefined
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 创建 ComputedRefImpl
const cRef = new ComputedRefImpl(getter, setter, isSSR)
// 开发模式下的调试选项
if (__DEV__ && debugOptions && !isSSR) {
cRef.onTrack = debugOptions.onTrack
cRef.onTrigger = debugOptions.onTrigger
}
return cRef as any
}
computed 返回的是 ComputedRefImpl 实例,它既是 Ref 也是 Subscriber
// packages/reactivity/src/watch.ts
export function watch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb?: WatchCallback | null,
options: WatchOptions = EMPTY_OBJ,
): WatchHandle {
const { immediate, deep, once, scheduler } = options
// 根据 source 类型创建 getter
let getter: () => any
if (isRef(source)) {
getter = () => source.value
} else if (isReactive(source)) {
getter = () => traverse(source)
} else if (isArray(source)) {
getter = () => source.map(s => isRef(s) ? s.value : ...)
} else if (isFunction(source)) {
getter = source
}
// 创建 effect
const effect = new ReactiveEffect(getter)
effect.scheduler = scheduler ? () => scheduler(job, false) : job
// job 函数
const job = (immediateFirstRun?: boolean) => {
const newValue = effect.run()
if (hasChanged(newValue, oldValue)) {
cb!(newValue, oldValue, onCleanup)
oldValue = newValue
}
}
// 首次执行
if (immediate) {
job(true)
} else {
oldValue = effect.run()
}
return watchHandle
}
// packages/reactivity/src/baseHandlers.ts
class BaseReactiveHandler {
get(target: Target, key: string | symbol, receiver: object): any {
// 1. 处理 ReactiveFlags
if (key === ReactiveFlags.IS_REACTIVE) return !this._isReadonly
if (key === ReactiveFlags.IS_READONLY) return this._isReadonly
if (key === ReactiveFlags.RAW) return target
// 2. 数组方法拦截
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && arrayInstrumentations[key]) {
return arrayInstrumentations[key]
}
// 3. Reflect.get
const res = Reflect.get(target, key, isRef(target) ? target : receiver)
// 4. track 依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 5. shallow 直接返回
if (isShallow) return res
// 6. ref 解包
if (isRef(res)) {
return targetIsArray && isIntegerKey(key) ? res : res.value
}
// 7. 深层响应式转换
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
// packages/reactivity/src/baseHandlers.ts
class MutableReactiveHandler {
set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = target[key]
// 1. 非 shallow 时,转换为原始值
if (!this._isShallow) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// 2. 判断是新增还是修改
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 3. 执行 set
const result = Reflect.set(target, key, value, receiver)
// 4. 触发更新(只有 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
}
}
// packages/reactivity/src/collectionHandlers.ts
function createInstrumentations(readonly: boolean, shallow: boolean) {
const instrumentations = {
get(this: MapTypes, key: unknown) {
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
// track 依赖
track(rawTarget, TrackOpTypes.GET, key)
// 包装返回值
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
return wrap(target.get(key))
},
set(this: MapTypes, key: unknown, value: unknown) {
const target = toRaw(this)
const hadKey = target.has(key)
const oldValue = target.get(key)
target.set(key, toRaw(value))
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return this
},
// ... add, delete, clear, forEach, entries, keys, values
}
return instrumentations
}
观察者模式是 Vue 响应式系统的核心模式,Dep 是被观察者 (Subject),ReactiveEffect 是观察者 (Observer)。
┌─────────────────────────────────────────────────────┐
│ Observer Pattern │
├─────────────────────────────────────────────────────┤
│ │
│ Dep (Subject) ReactiveEffect │
│ ┌─────────┐ ┌──────────┐ │
│ │ subs │ ←─── Link ───→ │ deps │ │
│ │ notify │ ────────────────→ update │ │
│ └─────────┘ └──────────┘ │
│ │ │ │
│ │ 1. 状态变化 │ │
│ │ 2. 通知所有观察者 │ │
│ └──────────────────────────────→ │
│ 3. 执行更新 │
└─────────────────────────────────────────────────────┘
代理模式通过 Proxy 对象拦截对原始对象的访问,在不修改原始对象的情况下添加响应式能力。
┌─────────────────────────────────────────────────────┐
│ Proxy Pattern │
├─────────────────────────────────────────────────────┤
│ │
│ User Code Proxy Handler │
│ ┌─────────┐ ┌──────────────┐ │
│ │ 读取属性 │ ──get──→ │ track() │ │
│ │ │ │ 依赖收集 │ │
│ │ 设置属性 │ ──set──→ │ trigger() │ │
│ │ │ │ 触发更新 │ │
│ └─────────┘ └──────────────┘ │
│ │ │
│ ↓ │
│ ┌──────────┐ │
│ │ 原始对象 │ │
│ │ (Target) │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────┘
发布订阅模式通过批处理机制 (batch) 解耦触发和执行,多个更新可以批量处理。
// 批处理机制
let batchDepth = 0
let batchedSub: Subscriber | undefined
export function startBatch(): void {
batchDepth++
}
export function endBatch(): void {
if (--batchDepth > 0) return
// 批量执行所有待处理的 effects
while (batchedSub) {
let e = batchedSub
batchedSub = undefined
while (e) {
const next = e.next
e.next = undefined
e.flags &= ~EffectFlags.NOTIFIED
if (e.flags & EffectFlags.ACTIVE) {
(e as ReactiveEffect).trigger()
}
e = next
}
}
}
┌──────────────────────────────────────────────────────────┐
│ 依赖收集流程 │
├──────────────────────────────────────────────────────────┤
│ 1. effect.run() 开始执行 │
│ └→ activeSub = effect │
│ │
│ 2. effect.fn() 执行用户函数 │
│ └→ 访问 reactive 对象属性 │
│ │
│ 3. Proxy.get 拦截器触发 │
│ └→ track(target, 'get', key) │
│ │
│ 4. dep.track() 收集依赖 │
│ └→ 创建 Link(activeSub, dep) │
│ └→ 添加到 dep.subs 链表 │
│ └→ 添加到 activeSub.deps 链表 │
│ │
│ 5. effect.fn() 执行完毕 │
│ └→ cleanupDeps() 清理未使用依赖 │
│ └→ activeSub = prevEffect │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ 触发更新流程 │
├──────────────────────────────────────────────────────────┤
│ 1. 修改 reactive 对象属性 │
│ └→ state.count = 2 │
│ │
│ 2. Proxy.set 拦截器触发 │
│ └→ Reflect.set(target, key, value) │
│ └→ trigger(target, 'set', key, value, oldValue) │
│ │
│ 3. dep.trigger() 触发更新 │
│ └→ version++ / globalVersion++ │
│ └→ startBatch() │
│ └→ 遍历 subs 链表 │
│ └→ sub.notify() │
│ └→ batch(sub) 加入队列 │
│ └→ endBatch() │
│ │
│ 4. 批量执行 effects │
│ └→ effect.trigger() │
│ └→ scheduler() 或 runIfDirty() │
│ └→ effect.run() 重新执行 │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Computed 惰性求值流程 │
├──────────────────────────────────────────────────────────┤
│ 1. 读取 computed.value │
│ └→ dep.track() 收集 computed 的订阅者 │
│ └→ refreshComputed(this) │
│ │
│ 2. refreshComputed 检查是否需要重新计算 │
│ └→ if (globalVersion === computed.globalVersion) │
│ └→ return // 无变化,跳过 │
│ └→ if (!isDirty(computed)) │
│ └→ return // 依赖未变化,跳过 │
│ │
│ 3. 需要重新计算 │
│ └→ activeSub = computed │
│ └→ const newValue = computed.fn(oldValue) │
│ └→ if (hasChanged(newValue, oldValue)) │
│ └→ computed._value = newValue │
│ └→ dep.version++ // 通知订阅者 │
│ │
│ 4. Computed 依赖变化时 │
│ └→ computed.notify() 设置 DIRTY 标志 │
│ └→ 传播到 computed 的订阅者 │
└──────────────────────────────────────────────────────────┘
// effect.run() 完整流程
run(): T {
// 1. 检查是否活跃
if (!(this.flags & EffectFlags.ACTIVE)) {
return this.fn()
}
// 2. 设置运行标志
this.flags |= EffectFlags.RUNNING
// 3. 执行清理函数
cleanupEffect(this)
// 4. 准备依赖(标记所有 link.version = -1)
prepareDeps(this)
// 5. 保存上下文,设置当前 effect
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
activeSub = this
shouldTrack = true
try {
// 6. 执行函数(触发依赖收集)
return this.fn()
} finally {
// 7. 清理未使用的依赖(version === -1 的 link)
cleanupDeps(this)
// 8. 恢复上下文
activeSub = prevEffect
shouldTrack = prevShouldTrack
this.flags &= ~EffectFlags.RUNNING
}
}
globalVersion 是一个全局计数器,每次响应式变化时递增。Computed 使用它快速判断是否需要重新计算。
// packages/reactivity/src/dep.ts
export let globalVersion = 0
// Dep.trigger() 中
trigger() {
this.version++
globalVersion++ // 全局版本递增
this.notify()
}
// ComputedRefImpl 中
class ComputedRefImpl {
globalVersion: number = globalVersion - 1
get value() {
refreshComputed(this)
return this._value
}
}
// refreshComputed 快速路径
export function refreshComputed(computed: ComputedRefImpl) {
// 如果全局版本未变,直接返回
if (computed.globalVersion === globalVersion) {
return // 无任何响应式变化,跳过重新计算
}
// 否则重新计算...
}
批处理确保在同一事件循环中的多次状态变更只触发一次更新,避免重复计算。
// 示例:多次修改只触发一次更新
state.a = 1 // startBatch()
state.b = 2 // depth = 2
state.c = 3 // depth = 3
// endBatch() depth = 2
// endBatch() depth = 1
// endBatch() depth = 0 → 执行所有 effects
// 批处理实现
let batchDepth = 0
let batchedSub: Subscriber | undefined
export function batch(sub: Subscriber): void {
sub.flags |= EffectFlags.NOTIFIED
sub.next = batchedSub
batchedSub = sub // 加入队列
}
export function endBatch(): void {
if (--batchDepth > 0) return
// 遍历队列,执行所有 effects
while (batchedSub) {
// ...执行 effect.trigger()
}
}
惰性计算意味着 computed 只在被读取时才计算,且只有在依赖变化后下次读取才重新计算。
// 惰性计算的好处
const expensive = computed(() => {
console.log('calculating...')
return heavyCalculation()
})
// 如果从未读取 expensive.value,永远不会执行计算
// 即使依赖变化,也只是标记为 DIRTY,不会立即计算
// 读取时才计算
console.log(expensive.value) // "calculating..." + 结果
console.log(expensive.value) // 直接返回缓存(如果依赖未变)
// 依赖变化后
state.count++ // 只标记 expensive 为 DIRTY,不计算
console.log(expensive.value) // 现在才重新计算
推荐做法
性能建议
1. 解构丢失响应性
// ❌ 错误
const { count } = state // count 是普通值
// ✅ 正确
const countRef = toRef(state, 'count')
// 或
watch(() => state.count, ...)
2. 替换 reactive 对象
// ❌ 错误
state = { ...newState } // 丢失响应性
// ✅ 正确
Object.assign(state, newState)
// 或使用 ref
const stateRef = ref(initialState)
stateRef.value = newState
// 1. 使用 onTrack / onTrigger
const state = reactive({ count: 0 })
watch(
() => state.count,
(val) => console.log('changed:', val),
{
onTrack(e) {
console.log('tracked:', e.key, e.target)
},
onTrigger(e) {
console.log('triggered:', e.key, e.newValue, e.oldValue)
}
}
)
// 2. 检查响应式类型
isRef(x) // 是否是 ref
isReactive(x) // 是否是 reactive
isReadonly(x) // 是否只读
isProxy(x) // 是否是代理
// 3. 获取原始值
toRaw(reactiveObj) // 获取原始对象
// 4. 标记不可响应
const raw = markRaw({ ... }) // 跳过响应式转换
相关主题
相关技术
核心要点
架构亮点
Vue 3 响应式系统是一个精心设计的响应式方案,通过 Proxy + 双向链表 + 批处理实现了高效、优雅的响应式能力。
源码地址
https://github.com/vuejs/core/tree/main/packages/reactivity