💚 Vue 3 响应式系统

基于 Proxy 的响应式原理深度解析

源码深度解读
2026-03-08 | Vue 3 Reactivity System

📑 目录

第一部分:基础概念

  • Vue 3 响应式简介
  • 核心架构设计
  • 响应式演进历史

第二部分:核心概念

  • 响应式对象 (reactive)
  • 引用类型 (ref)
  • 计算属性 (computed)
  • 依赖追踪系统

第三部分:核心实现

  • 关键类与接口
  • 核心函数解析
  • Proxy Handlers

第四部分:进阶

  • 设计模式应用
  • 数据流与时序图
  • 性能优化策略
  • 最佳实践与调试

💚 Vue 3 响应式系统简介

Vue 3 的响应式系统是基于 ES6 Proxy 实现的全新响应式方案,相比 Vue 2 的 Object.defineProperty 方案有质的飞跃。

核心特性

  • 基于 Proxy 的依赖追踪
  • 惰性求值 (Lazy Evaluation)
  • 自动内存管理
  • 更好的 TypeScript 支持

主要优势

  • 支持 Map/Set/WeakMap/WeakSet
  • 数组索引和长度变化检测
  • 无需 $set/$delete
  • 更小的运行时开销

🏗️ 核心架构

┌─────────────────────────────────────────────────────────┐
│                    @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 vs Vue 3 响应式对比

特性 Vue 2 Vue 3
实现方式 Object.defineProperty Proxy
数组支持 需要重写方法 原生支持
Map/Set ❌ 不支持 ✅ 完全支持
属性添加 需要 Vue.set 自动响应
性能 初始化时全量遍历 惰性代理

🎯 核心概念总览

响应式原语

  • reactive() - 深层响应式对象
  • ref() - 任意值的响应式包装
  • readonly() - 只读代理
  • shallowReactive() - 浅层响应式

计算与侦听

  • computed() - 惰性计算属性
  • watch() - 数据变化侦听
  • watchEffect() - 自动依赖收集
  • effect() - 底层副作用

📦 响应式对象 (reactive)

// 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 代理,所有属性访问都会被拦截

🔗 Ref 实现

// 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()  // 触发更新
    }
  }
}

⚡ 计算属性 (computed)

// 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    │  │   │  │
│  │                   │       │ ...             │  │   │  │
│  │                   │       └─────────────────┘  │   │  │
│  │                   └────────────────────────────┘   │  │
│  └────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────┘

🎯 Target 接口

// 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 接口定义了响应式对象的内部标记,用于识别和处理不同类型的对象

🚩 ReactiveFlags 枚举

// 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 标记
}

使用场景

  • isReactive() 检查
  • isReadonly() 检查
  • toRaw() 获取原始值

注意事项

  • 避免手动修改这些标记
  • 以 __v_ 开头的是内部属性

📦 Dep 类 - 依赖管理

// 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()
  }
}

🔗 Link 类 - 双向链表节点

// 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 管理依赖。这使得依赖清理非常高效。

⚡ ReactiveEffect 类

// 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)               // 加入批处理队列
    }
  }
}

🏁 EffectFlags 枚举

// 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 防止重复加入批处理队列

👤 Subscriber 接口

// 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 的依赖。

🔗 RefImpl 类详解

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()
    }
  }
}

⚡ ComputedRefImpl 类详解

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
  }
}

🌐 EffectScope 类

// 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)
        }
      }
    }
  }
}

🎭 ProxyHandler 类层次

// 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 }
}

🔧 createReactiveObject 函数

// 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
}

📦 reactive 函数家族

// 响应式
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,
  )
}

🔗 ref 函数家族

// 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)
}

🎯 track 函数 - 依赖收集

// 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 的依赖关系

💥 trigger 函数 - 触发更新

// 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()
}

⚡ effect 函数

// 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()
}

⚡ computed 函数

// 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

👁️ watch 函数

// 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
}

🎭 baseHandlers - get 拦截

// 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
  }
}

🎭 baseHandlers - set 拦截

// 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
  }
}

🎭 collectionHandlers - Map/Set 处理

// 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 更新流程

┌──────────────────────────────────────────────────────────┐
│               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 执行流程详解

// 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)  // 现在才重新计算

✅ 最佳实践

推荐做法

  • 使用 reactive 处理对象
  • 使用 ref 处理原始值
  • 使用 computed 派生状态
  • 使用 EffectScope 管理副作用
  • 解包 ref 时使用 unref

性能建议

  • 避免在 computed 中执行副作用
  • 大列表使用 shallowRef
  • 合理使用 markRaw 跳过响应式
  • 及时停止不需要的 watch/effect
  • 使用 triggerRef 手动触发 shallowRef

⚠️ 常见陷阱

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({ ... })  // 跳过响应式转换

📚 扩展阅读

官方文档

RFCs

相关主题

  • ES6 Proxy 深入
  • 观察者模式实现
  • 依赖追踪算法
  • 惰性求值策略
  • 内存管理与 WeakMap

相关技术

  • MobX 响应式原理
  • Svelte 编译时响应式
  • Signals (Solid/Preact)

🎯 总结

核心要点

  • Proxy 是响应式的基石
  • Dep/Link 实现依赖追踪
  • ReactiveEffect 管理副作用
  • computed 惰性求值
  • 批处理 优化性能

架构亮点

  • 双向链表高效管理依赖
  • 全局版本号快速判断
  • WeakMap 自动内存管理
  • EffectScope 作用域控制
  • TypeScript 完整类型支持

Vue 3 响应式系统是一个精心设计的响应式方案,通过 Proxy + 双向链表 + 批处理实现了高效、优雅的响应式能力。

💚 感谢阅读

Vue 3 Reactivity System Deep Dive

源码地址
https://github.com/vuejs/core/tree/main/packages/reactivity

访问链接: https://atcfu.com/ai-articles/vue3-reactivity/