Vue.js 源码揭秘(二):响应式系统

23 阅读3分钟

Vue.js 源码揭秘(二):响应式系统

本文深入 reactivity 包源码,解析 reactive、ref、effect 的实现原理。

一、响应式概览

┌─────────────────────────────────────────────────────────────┐
│                    响应式系统                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   reactive(obj)  ──► Proxy ──► get: track / set: trigger   │
│                                                             │
│   ref(value)     ──► RefImpl ──► get: track / set: trigger │
│                                                             │
│   effect(fn)     ──► ReactiveEffect ──► 自动收集依赖        │
│                                                             │
│   computed(fn)   ──► ComputedRefImpl ──► 惰性求值 + 缓存    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、reactive 实现

2.1 createReactiveObject

// packages/reactivity/src/reactive.ts
export function reactive(target) {
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

function createReactiveObject(
  target,
  isReadonly,
  baseHandlers,
  collectionHandlers,
  proxyMap
) {
  // 非对象直接返回
  if (!isObject(target)) {
    return target
  }
  
  // 已经是 Proxy,直接返回
  if (target[ReactiveFlags.RAW]) {
    return target
  }
  
  // 已有缓存,返回缓存
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  // 获取目标类型
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  
  // 创建 Proxy
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION 
      ? collectionHandlers  // Map/Set
      : baseHandlers        // Object/Array
  )
  
  // 缓存
  proxyMap.set(target, proxy)
  return proxy
}

2.2 baseHandlers

// packages/reactivity/src/baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {
  get(target, key, receiver) {
    // 特殊 key 处理
    if (key === ReactiveFlags.IS_REACTIVE) return true
    if (key === ReactiveFlags.RAW) return target
    
    const res = Reflect.get(target, key, receiver)
    
    // 依赖收集
    track(target, TrackOpTypes.GET, key)
    
    // 深层响应式
    if (isObject(res)) {
      return reactive(res)
    }
    
    return res
  },
  
  set(target, key, value, receiver) {
    const oldValue = target[key]
    const result = Reflect.set(target, key, value, receiver)
    
    // 触发更新
    if (hasChanged(value, oldValue)) {
      trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
    
    return result
  },
  
  deleteProperty(target, key) {
    const hadKey = hasOwn(target, key)
    const result = Reflect.deleteProperty(target, key)
    
    if (result && hadKey) {
      trigger(target, TriggerOpTypes.DELETE, key)
    }
    
    return result
  }
}

三、Dep 依赖容器

// packages/reactivity/src/dep.ts
export class Dep {
  version = 0
  activeLink?: Link
  subs?: Link        // 订阅者链表尾
  subsHead?: Link    // 订阅者链表头
  map?: KeyToDepMap
  key?: unknown
  sc: number = 0     // 订阅者计数
  
  constructor(public computed?: ComputedRefImpl) {}
  
  track() {
    if (!activeSub || !shouldTrack) return
    
    let link = this.activeLink
    if (!link || link.sub !== activeSub) {
      // 创建新的 Link
      link = {
        dep: this,
        sub: activeSub,
        version: this.version,
        nextDep: undefined,
        prevDep: activeSub.depsTail,
        nextSub: undefined,
        prevSub: this.subs,
        prevActiveLink: this.activeLink
      }
      
      // 添加到订阅者的依赖链表
      if (activeSub.depsTail) {
        activeSub.depsTail.nextDep = link
      } else {
        activeSub.deps = link
      }
      activeSub.depsTail = link
      
      // 添加到 Dep 的订阅者链表
      if (this.subs) {
        this.subs.nextSub = link
      }
      this.subs = link
      this.sc++
    }
    
    link.version = this.version
    this.activeLink = link
  }
  
  trigger() {
    this.version++
    
    // 通知所有订阅者
    for (let link = this.subs; link; link = link.prevSub) {
      link.sub.notify()
    }
  }
}

四、ReactiveEffect

// packages/reactivity/src/effect.ts
export let activeSub: Subscriber | undefined

export class ReactiveEffect {
  deps?: Link
  depsTail?: Link
  flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
  
  constructor(public fn: () => any) {
    // 注册到 effectScope
    if (activeEffectScope?.active) {
      activeEffectScope.effects.push(this)
    }
  }
  
  run() {
    if (!(this.flags & EffectFlags.ACTIVE)) {
      return this.fn()
    }
    
    this.flags |= EffectFlags.RUNNING
    cleanupEffect(this)
    prepareDeps(this)
    
    const prevEffect = activeSub
    const prevShouldTrack = shouldTrack
    activeSub = this
    shouldTrack = true
    
    try {
      return this.fn()
    } finally {
      cleanupDeps(this)
      activeSub = prevEffect
      shouldTrack = prevShouldTrack
      this.flags &= ~EffectFlags.RUNNING
    }
  }
  
  notify() {
    if (this.flags & EffectFlags.RUNNING) return
    if (!(this.flags & EffectFlags.NOTIFIED)) {
      batch(this)
    }
  }
  
  stop() {
    if (this.flags & EffectFlags.ACTIVE) {
      for (let link = this.deps; link; link = link.nextDep) {
        removeSub(link)
      }
      this.deps = this.depsTail = undefined
      cleanupEffect(this)
      this.flags &= ~EffectFlags.ACTIVE
    }
  }
}

4.1 effect 函数

export function effect(fn, options?) {
  const e = new ReactiveEffect(fn)
  
  if (options) {
    extend(e, options)
  }
  
  e.run()
  
  const runner = e.run.bind(e)
  runner.effect = e
  return runner
}

五、ref 实现

// packages/reactivity/src/ref.ts
class RefImpl {
  _value: any
  _rawValue: any
  dep: Dep = new Dep()
  
  readonly [ReactiveFlags.IS_REF] = true
  
  constructor(value, isShallow) {
    this._rawValue = isShallow ? value : toRaw(value)
    this._value = isShallow ? value : toReactive(value)
  }
  
  get value() {
    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 function ref(value) {
  return createRef(value, false)
}

function createRef(rawValue, shallow) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

六、computed 实现

// packages/reactivity/src/computed.ts
class ComputedRefImpl {
  _value: any
  dep: Dep
  effect: ReactiveEffect
  
  readonly [ReactiveFlags.IS_REF] = true
  readonly [ReactiveFlags.IS_READONLY]: boolean
  
  constructor(getter, setter, isReadonly) {
    this.dep = new Dep(this)
    this[ReactiveFlags.IS_READONLY] = isReadonly
    
    this.effect = new ReactiveEffect(() => getter(this._value))
    this.effect.computed = this
    this.effect.scheduler = () => {
      // 依赖变化时,标记为脏
      if (!(this.flags & EffectFlags.DIRTY)) {
        this.flags |= EffectFlags.DIRTY
        this.dep.trigger()
      }
    }
  }
  
  get value() {
    // 脏检查,需要重新计算
    if (this.flags & EffectFlags.DIRTY) {
      this._value = this.effect.run()
      this.flags &= ~EffectFlags.DIRTY
    }
    
    this.dep.track()
    return this._value
  }
  
  set value(newValue) {
    this._setter(newValue)
  }
}

export function computed(getterOrOptions) {
  let getter, setter
  
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  
  return new ComputedRefImpl(getter, setter, !setter)
}

七、批量更新

// packages/reactivity/src/effect.ts
let batchDepth = 0
let batchedSub: Subscriber | undefined

export function batch(sub: Subscriber) {
  sub.flags |= EffectFlags.NOTIFIED
  sub.next = batchedSub
  batchedSub = sub
}

export function startBatch() {
  batchDepth++
}

export function endBatch() {
  if (--batchDepth > 0) return
  
  let error
  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) {
        try {
          e.trigger()
        } catch (err) {
          if (!error) error = err
        }
      }
      e = next
    }
  }
  
  if (error) throw error
}

八、依赖清理

function prepareDeps(sub: Subscriber) {
  for (let link = sub.deps; link; link = link.nextDep) {
    // 标记为待清理
    link.version = -1
    link.prevActiveLink = link.dep.activeLink
    link.dep.activeLink = link
  }
}

function cleanupDeps(sub: Subscriber) {
  let head
  let tail = sub.depsTail
  let link = tail
  
  while (link) {
    const prev = link.prevDep
    
    if (link.version === -1) {
      // 未使用的依赖,移除
      if (link === tail) tail = prev
      removeSub(link)
      removeDep(link)
    } else {
      head = link
    }
    
    link.dep.activeLink = link.prevActiveLink
    link.prevActiveLink = undefined
    link = prev
  }
  
  sub.deps = head
  sub.depsTail = tail
}

九、响应式工具函数

// 判断是否为响应式
export function isReactive(value) {
  return !!(value && value[ReactiveFlags.IS_REACTIVE])
}

// 判断是否为 ref
export function isRef(r) {
  return r ? r[ReactiveFlags.IS_REF] === true : false
}

// 获取原始值
export function toRaw(observed) {
  const raw = observed && observed[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

// 标记为非响应式
export function markRaw(value) {
  def(value, ReactiveFlags.SKIP, true)
  return value
}

// 解包 ref
export function unref(ref) {
  return isRef(ref) ? ref.value : ref
}

十、调试技巧

10.1 关键断点

// 依赖收集
packages/reactivity/src/dep.tsDep.track

// 触发更新
packages/reactivity/src/dep.tsDep.trigger

// effect 执行
packages/reactivity/src/effect.tsReactiveEffect.run

10.2 调试示例

import { reactive, effect } from 'vue'

const state = reactive({ count: 0 })

effect(() => {
  console.log('count:', state.count)
  // 断点:查看 activeSub、deps
})

state.count++  // 断点:查看 trigger 流程

十一、小结

Vue3 响应式系统的核心:

  1. Proxy:拦截对象操作,实现依赖收集和触发
  2. Dep:依赖容器,管理订阅者链表
  3. ReactiveEffect:副作用,自动追踪依赖
  4. 双向链表:高效的依赖管理和清理
  5. 批量更新:合并多次更新,提升性能

📦 源码地址:github.com/vuejs/core

下一篇:虚拟 DOM 与 Diff 算法

如果觉得有帮助,欢迎点赞收藏 👍