Skip to content

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 实现了更强大和灵活的响应式能力:

  1. Proxy 优势:可以拦截更多操作,支持数组索引和 length 属性
  2. 依赖收集:通过 WeakMap 建立目标对象、属性和副作用函数的关系
  3. 性能优化:使用位运算优化依赖标记,支持嵌套 effect
  4. 类型安全:完整的 TypeScript 支持

理解响应式原理有助于我们:

  • 写出更高效的 Vue 应用
  • 避免常见的响应式陷阱
  • 进行性能优化和调试
  • 深入理解 Vue 3 的设计思想

相关文章

用代码构建未来,让技术改变世界 🚀 | 专注前端技术,分享开发经验