技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

Vue 3 源码:响应式系统、依赖收集和派发更新流程浅析

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/29-Vue3源码-响应式系统-依赖收集和派发更新流程浅析
本文目录15 个章节
  1. 本文内容
  2. 一、前置知识:Proxy 和 Reflect
  3. 二、reactive 和 Proxy handler
  4. 三、Vue 3.5 的依赖关系模型
  5. 四、ReactiveEffect:响应式副作用
  6. 五、Vue 3.2 及更早版本的 marker bits(历史实现)
  7. 六、computed 的响应式分析
  8. 七、watch 和 watchEffect
  9. 八、cleanup 的两个层次
  10. 九、Scheduler 与父子组件更新
  11. 十、target === toRaw(receiver) 与响应式原型链
  12. 十一、文章中的旧源码问题总结
  13. 十二、总结
  14. 官方参考
  15. 原文出处

Vue 3 源码:响应式系统、依赖收集和派发更新流程浅析

原文包含 Vue 3.2 及更早版本的源码片段。Vue 3.5 对响应式依赖图、computed 和 Scheduler 做过结构性调整,原文中的 dep.wdep.neffectTrackDepthmaxMarkerBits、旧版 ReactiveEffect.parent 和旧 pre 队列不能继续标注为 Vue 3.5 源码。

本文保留原文“从 Proxy/Reflect 进入 track,再通过 trigger 派发到 effect、computed 和 watch”的分析主线,并把当前源码和历史实现分开。当前源码基线为 Vue Core v3.5.39

本文内容

  • ProxyReflect 的基本语义;
  • Vue 3.5 响应式依赖图与 track / trigger
  • ReactiveEffect 的运行、清理、批处理和调度;
  • computed 的依赖传播和缓存;
  • watchwatchEffect、cleanup 与 flush 时机;
  • Vue 3.2 及更早版本 marker bits 实现的历史对照;
  • Reflect 的 receiver 和响应式原型链专题。

一、前置知识:Proxy 和 Reflect

Vue 2 主要通过 Object.defineProperty() 转换已有属性;Vue 3 使用 Proxy 代理对象,并在代理的读取、写入、删除、判断和枚举等操作中建立或触发依赖。

Proxy 是什么?

Proxy 为对象创建代理,可以拦截属性读取、赋值、删除、in 判断、枚举和函数调用等操作。Proxy 只能代理对象,不能直接代理 stringnumbernullundefined 等原始值。

const target = { count: 1 }

const proxy = new Proxy(target, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver)
  },
  deleteProperty(target, key) {
    return Reflect.deleteProperty(target, key)
  },
  has(target, key) {
    return Reflect.has(target, key)
  },
  ownKeys(target) {
    return Reflect.ownKeys(target)
  },
})

Vue 3 的真实 handler 还会处理 readonly、shallow、数组方法、ref 解包、Map/Set collection handler、内建 Symbol 和响应式标记等情况,因此上面只是标准 Proxy 的最小示意。

Reflect 是什么?

Reflect 是 JavaScript 内置对象,提供与 Proxy trap 对应的标准内部操作,例如:

Reflect.get(target, key, receiver)
Reflect.set(target, key, value, receiver)
Reflect.deleteProperty(target, key)
Reflect.has(target, key)
Reflect.ownKeys(target)

Reflect 的方法通常返回明确结果:

  • Reflect.set() 返回赋值是否成功的 boolean;
  • Reflect.deleteProperty() 返回删除是否成功的 boolean;
  • Reflect.get() 返回读取结果;
  • 失败时会遵循对应的标准内部方法语义。

不能笼统地说 Reflect 方法只是 Object 方法的“有返回值版本”。涉及继承 getter/setter、receiver 或失败处理时,二者的可观察行为可能不同。

receiver 为什么重要?

const obj = {
  count: 22,
  get count1() {
    return this.count
  },
}

const p = new Proxy(obj, {
  get(target, key, receiver) {
    // 错误的转发:getter 中的 this 会指向 target
    return target[key]
  },
})

const p1 = {
  __proto__: p,
  count: 9999,
}

console.log(p1.count1) // 22

上例中,target[key] 直接访问 obj.count1,getter 内的 thisobj,所以得到 22。使用 Reflect 转发:

const p = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
})

const p1 = {
  __proto__: p,
  count: 9999,
}

console.log(p1.count1) // 9999

Reflect.get(target, key, receiver) 会把 receiver 作为 getter 的 this。这对 Vue 保留响应式代理的 getter/setter 语义很重要,也能避免继承场景下把依赖错误地收集到原始对象。

二、reactive 和 Proxy handler

Vue 3.5 的 reactive() 入口可以概括为:

function reactive(target) {
  if (isReadonly(target)) return target

  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}

真实实现还会检查:

  • target 是否为对象;
  • 是否已经是某种响应式代理;
  • 是否命中 reactiveMap 缓存;
  • 是否是可代理的普通对象、数组或集合;
  • 是否使用了 markRaw() 或不可扩展对象。

普通对象/数组使用 baseHandlers,Map、Set、WeakMap、WeakSet 使用 collection handlers。嵌套对象通常在读取时惰性地转换为 reactive,而不是一开始递归创建所有 Proxy。

当前 get 的简化流程

function get(target, key, receiver) {
  if (key === ReactiveFlags.RAW) {
    return target
  }

  const result = Reflect.get(
    target,
    key,
    isRef(target) ? target : receiver,
  )

  if (!isReadonly && isTrackableKey(key)) {
    track(target, TrackOpTypes.GET, key)
  }

  if (isShallow) return result

  if (isRef(result)) {
    // 数组整数索引不自动解包 ref
    return isArray(target) && isIntegerKey(key)
      ? result
      : result.value
  }

  return isObject(result)
    ? isReadonly ? readonly(result) : reactive(result)
    : result
}

这段代码只用于解释顺序。当前源码还会处理内建 Symbol、数组 instrumentation、readonly、shallow 和 hasOwnProperty 等情况。具体见 baseHandlers.ts v3.5.39

set 的关键判断

function set(target, key, value, receiver) {
  const oldValue = target[key]
  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) && result) {
    if (!hadKey) {
      trigger(target, TriggerOpTypes.ADD, key, value)
    } else if (!Object.is(value, oldValue)) {
      trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
  }

  return result
}

真实 Vue 3.5 handler 还会处理 raw 值、ref 属性赋值、shallow/readonly 和数组索引。核心语义是:

  • 新增 key 是 ADD
  • 已有 key 且 Object.is 判断值发生变化才是 SET
  • NaNNaN 不被视为变化;
  • Reflect.set 成功并且当前 receiver 对应真正 raw target 时才触发通知;
  • deleteProperty 对成功删除的已有 key 触发 DELETE
  • hasownKeys 也会收集相应依赖。

三、Vue 3.5 的依赖关系模型

高层模型

响应式 Proxy get/has/ownKeys
  -> track(rawTarget, operationType, key)
  -> 找到或创建 Dep
  -> 当前 activeSub 与 Dep 建立 Link

响应式 Proxy set/delete/Map/Set 操作
  -> trigger(rawTarget, operationType, key, ...)
  -> 根据 SET/ADD/DELETE/CLEAR、数组 length、迭代 key 找到 Dep
  -> Dep.version++、globalVersion++
  -> Dep.notify() 批量通知 subscriber
  -> ReactiveEffect / ComputedRefImpl 决定 scheduler 或重新计算

Vue 3.5.39 的核心依赖表仍然可以从外层理解为:

targetMap: WeakMap<RawTarget, Map<Key, Dep>>

Dep 不再是简单的 Set<ReactiveEffect>,而是维护版本号和 subscriber 链;依赖关系通过 Link 表示:

Dep <-> Link <-> Subscriber

一个 Link 同时属于:
- subscriber 的 deps / depsTail 双向链
- dep 的 subs / subsHead 双向链

这样同一条依赖边可以高效地从 source 和 subscriber 两侧清理。Link.version 记录依赖版本,Dep.version 在触发时递增。

track

export function track(target, type, key) {
  if (shouldTrack && activeSub) {
    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 Dep()))
      dep.map = depsMap
      dep.key = key
    }

    dep.track({ target, type, key })
  }
}

开发构建会传入调试信息,生产构建会省略一部分调试对象。Dep.track() 的核心工作是:

  1. 查看当前 activeSub 是否已经通过 activeLink 与这个 Dep 连接;
  2. 没有连接时创建 Link(activeSub, dep)
  3. 把 Link 加入 active subscriber 的 deps 链;
  4. 把 Link 加入 Dep 的 subscribers 链;
  5. 已有但在本轮被标记为过期的 Link,则同步当前 Dep 的 version。

trigger

export function trigger(target, type, key, newValue, oldValue, oldTarget) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    globalVersion++
    return
  }

  startBatch()

  if (type === TriggerOpTypes.CLEAR) {
    depsMap.forEach(dep => dep.trigger())
  } else {
    run(depsMap.get(key))

    if (isArray(target) && isIntegerKey(key)) {
      run(depsMap.get(ARRAY_ITERATE_KEY))
    }

    if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
      run(depsMap.get(ITERATE_KEY))
    }
  }

  endBatch()
}

这是概念化代码。真实实现还会根据数组 length、Map 的 key iteration、Map SET、Set/Map CLEAR 等情况选择不同 Dep。依赖“遍历对象/数组/集合”的 effect,不仅依赖某一个具体 key,还依赖对应的迭代 key。

Dep.trigger() 会增加 dep.version 和全局 globalVersion,然后在 startBatch() / endBatch() 中通知 subscriber。它不再像旧版本那样先把多个 Dep 展平成一个 Set,再直接逐个 effect.run()

四、ReactiveEffect:响应式副作用

创建与首次运行

effect() 会创建一个 ReactiveEffect,运行它并返回可以再次调用的 runner:

const runner = effect(() => {
  console.log(state.count)
})

runner()

Vue 3.5 的 ReactiveEffect 关键字段包括:

class ReactiveEffect {
  deps?: Link
  depsTail?: Link
  flags = EffectFlags.ACTIVE | EffectFlags.TRACKING
  cleanup?: () => void
  scheduler?: EffectScheduler
  onStop?: () => void
}

run() 的当前流程

run() {
  if (!(this.flags & ACTIVE)) {
    return this.fn()
  }

  this.flags |= RUNNING
  cleanupEffect(this) // 执行低层 onEffectCleanup 清理
  prepareDeps(this)   // 将已有 Link.version 暂时置为 -1

  const previousSub = activeSub
  const previousTracking = shouldTrack
  activeSub = this
  shouldTrack = true

  try {
    return this.fn()
  } finally {
    cleanupDeps(this)  // 删除本轮没有重新读取的 Link
    activeSub = previousSub
    shouldTrack = previousTracking
    this.flags &= ~RUNNING
  }
}

当前实现通过 activeSub 保存和恢复嵌套上下文,通过 Link 的 version 判断动态依赖是否仍然有效:

const state = reactive({ ok: true, a: 1, b: 2 })

effect(() => {
  console.log(state.ok ? state.a : state.b)
})

ok 从 true 变成 false,下一次 effect 运行读取 b,没有重新读取的 a 对应 Link 会在 cleanup 阶段移除。这样 effect 不会继续订阅已经不再使用的分支。

trigger() 如何触发 effect

trigger() -> dep.trigger() -> dep.notify()
        -> batch subscriber
        -> ReactiveEffect.trigger()
             ├─ PAUSED:放入 paused queue
             ├─ 有 scheduler:调用 scheduler
             └─ 否则:runIfDirty() -> run()

因此不能把 Vue 3.5 简化为“有 scheduler 就调用 scheduler,否则无条件 effect.run()”。当前还有 dirty/version 检查、computed subscriber、批处理、暂停和递归更新 flags。

停止 effect

import { effect, stop } from 'vue'

const runner = effect(() => {
  console.log(state.count)
})

stop(runner)

effect() 返回的是可以再次运行 effect 的 runner,不是 stop 函数。公共 stop(runner) 会从各个 Dep 的 subscriber 链中移除当前 effect 的 Link,清空 deps,运行 onStop 和底层 cleanup,并清除 ACTIVE 标志。

嵌套 effect 的注意点

原文中的以下写法是原生 Proxy:

const obj = new Proxy({ count: 1 }, {})

它不会接入 Vue 的 track() / trigger(),因此不能用来证明 Vue effect 的依赖行为。若要使用 Vue 响应式,应写:

const obj = reactive({ count: 1 })

effect(() => {
  console.log(obj.count)
})

Vue 3.5 通过 activeSubshouldTrackDep.activeLink 和 Link 的上下文恢复处理嵌套依赖,而不是原文旧代码中的 ReactiveEffect.parent 链。还要避免在外层 effect 每次运行时反复创建新的内层 effect,否则会积累多个 subscriber;需要动态管理时应使用明确的 effect scope 和 stop handle。

五、Vue 3.2 及更早版本的 marker bits(历史实现)

原文大量分析的 dep.wdep.ntrackOpBiteffectTrackDepthmaxMarkerBits = 30,属于 Vue 3.2 时代的依赖清理优化。它的思路是:每轮 effect 运行前标记旧依赖,运行中标记新依赖,运行后删除“旧有但本轮未重新读取”的依赖。

简化历史流程:

run()
  -> initDepMarkers(): dep.w |= trackOpBit
  -> 执行 fn(),读取响应式值
  -> trackEffects(): dep.n |= trackOpBit
  -> finalizeDepMarkers()
       dep.w 有、dep.n 无 -> 删除 effect

当嵌套深度超过位宽时,旧实现会退化到完整清理。这个模型有学习价值,但不能用于描述 Vue 3.5.39:Vue 3.5 使用 Dep.versionLink 双向链和 prepareDeps() / cleanupDeps(),不再依赖 marker bits,也没有 maxMarkerBits

原文历史流程图(Vue 3.2 时代)

以下七张图来自原文,已下载到本地。它们用于保留文章的历史阅读线索,图中的依赖 Set、旧版 ReactiveEffect、旧 computed 或旧 Scheduler 结构不能当作 Vue 3.5.39 当前实现

原文依赖收集流程图,Vue 3.2 历史资料(本地化)

原文派发更新流程图,Vue 3.2 历史资料(本地化)

原文 ReactiveEffect 流程图,Vue 3.2 历史资料(本地化)

原文 computed 依赖图,Vue 3.2 历史资料(本地化)

原文 computed 派发更新图,Vue 3.2 历史资料(本地化)

原文 watch/watchEffect 依赖图,Vue 3.2 历史资料(本地化)

原文 watch/watchEffect 派发更新图,Vue 3.2 历史资料(本地化)

六、computed 的响应式分析

computed 的公共语义

computed 是有缓存的派生值:

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

只有读取 computed 时才会求值;依赖没有发生有效变化时,会复用缓存。也可以创建可写 computed:

const count = ref(1)

const plusOne = computed({
  get: () => count.value + 1,
  set: value => {
    count.value = value - 1
  },
})

Vue 3.5 的 ComputedRefImpl

Vue 3.2 时代的 computed 使用独立 ReactiveEffect_dirtytrackRefValuetriggerRefValue;Vue 3.5 的实现已改为 ComputedRefImpl 自身就是 subscriber:

class ComputedRefImpl implements Subscriber {
  readonly dep = new Dep(this)
  deps?: Link
  depsTail?: Link
  flags = EffectFlags.DIRTY
  globalVersion = globalVersion - 1

  notify() {
    this.flags |= EffectFlags.DIRTY
    if (!(this.flags & NOTIFIED) && activeSub !== this) {
      batch(this, true)
      return true
    }
  }

  get value() {
    const link = this.dep.track()
    refreshComputed(this)
    if (link) link.version = this.dep.version
    return this._value
  }
}

当前 computed 依赖流程

读取 computed.value
  -> computed.dep.track()
  -> refreshComputed(computed)
  -> activeSub = computed
  -> 执行 getter
  -> getter 读取的响应式 Dep 与 computed 建立 Link
  -> 恢复 activeSub,返回缓存值

当 source Dep 发生变化:

source Dep.trigger()
  -> computed.notify(),设置 DIRTY
  -> computed.dep.notify(),通知下游 render/effect
  -> batch 结束
  -> 下游 effect 根据 computed 依赖版本判断是否 dirty
  -> refreshComputed,重新运行 getter
  -> 只有结果确实变化时才增加 computed.dep.version

因此“computed 依赖变化就一定直接让渲染 effect 重跑”过于绝对。Vue 3.5 会结合 globalVersion、Dep version 和计算结果稳定性减少无效更新。getter 还可以接收上一次计算结果作为参数,这是较新的 API 能力。

旧 computed 源码的版本边界

下面这类代码属于 Vue 3.2/更早版本的历史实现:

this._dirty = true
this.effect = new ReactiveEffect(getter, () => {
  if (!this._dirty) {
    this._dirty = true
    triggerRefValue(this)
  }
})

它可以帮助理解“依赖变化使缓存失效”的抽象,但不要把 _dirty_cacheabletriggerRefValue 和独立 ReactiveEffect 写成 Vue 3.5.39 的当前源码。

七、watch 和 watchEffect

公共 API 的区别

watch(source, callback, options)
watchEffect(effect, options)
  • watch 显式指定 source,默认惰性执行,callback 收到 new value、old value 和 cleanup。
  • watchEffect 立即执行,并自动追踪 callback 同步执行期间读取的依赖。
  • watch 的 callback 内部读取的响应式值不会自动成为 watch source。
  • watchEffect(async () => {}) 只追踪第一个 await 之前同步读取的依赖。

watch 支持的 source

const count = ref(0)
const state = reactive({ name: 'Vue' })

watch(count, value => {})
watch(() => state.name, value => {})
watch(state, value => {})
watch([count, () => state.name], ([newCount, newName]) => {})

不能直接把 reactive 对象的属性值传进去:

watch(state.name, callback) // ❌ 传入的是普通字符串
watch(() => state.name, callback) // ✅ 使用 getter

直接 watch 一个 reactive object 默认会深度遍历;Vue 3.5 还支持 deep: falsedeep: 0deep: number 控制遍历范围。深度 watch 需要遍历对象,数据量大时会产生明显成本。

getter 的简化流程

if (isRef(source)) {
  getter = () => source.value
} else if (isReactive(source)) {
  getter = () => reactiveGetter(source)
} else if (isFunction(source)) {
  getter = cb
    ? () => source()
    : () => runWatchEffect(source, onCleanup)
} else if (isArray(source)) {
  getter = () => source.map(normalizeSource)
}

当前实现还会区分 shallow source、deep 数字深度、错误处理和多源 old value。这里只展示设计结构。

watch 的 job

const job = (immediateFirstRun = false) => {
  // Vue 3.5 内部使用 EffectFlags.ACTIVE;这里省略导入和错误处理
  if (
    (!(effect.flags & EffectFlags.ACTIVE)) ||
    (!effect.dirty && !immediateFirstRun)
  ) {
    return
  }

  const newValue = effect.run()
  const changed =
    immediateFirstRun ||
    deep ||
    forceTrigger ||
    hasChanged(newValue, oldValue)

  if (changed) {
    cleanup()
    callback(newValue, oldValue, onCleanup)
    oldValue = newValue
  }
}

真实 Vue 3.5 代码还会处理多 source、数组 old value、once、错误处理和 cleanupMap。watch 返回的 handle 不只是一个 stop 函数,还具有:

const handle = watch(source, callback)

handle()       // stop
handle.stop()
handle.pause()
handle.resume()

watchEffect 和 cleanup

watchEffect(onCleanup => {
  const controller = new AbortController()

  fetch(`/api/${id.value}`, {
    signal: controller.signal,
  })

  onCleanup(() => {
    controller.abort()
  })
})

每次 watchEffect 即将重新运行前,Vue 会先执行上一次注册的 cleanup;停止 watcher 时也会执行 cleanup。

Vue 3.5+ 还可以使用 onWatcherCleanup()

import { onWatcherCleanup, watch } from 'vue'

watch(id, newId => {
  const controller = new AbortController()

  fetch(`/api/${newId}`, { signal: controller.signal })
  onWatcherCleanup(() => controller.abort())
})

onWatcherCleanup() 必须在 watch callback 或 watchEffect 的同步执行阶段调用,不能在 await 之后注册。兼容写法仍然是 callback 第三个参数的 onCleanup

watch(id, (newId, oldId, onCleanup) => {
  const controller = new AbortController()
  onCleanup(() => controller.abort())
})

Vue 3.5 watch 的调度模式

flush: 'sync'
  source 变化时同步执行,不进入 Scheduler,也没有批处理

flush: 'pre'(默认)
  首次执行可以直接运行;组件挂载后,job 进入带 PRE flag 的主队列
  通常在 owner 组件 DOM 更新之前执行

flush: 'post'
  进入 post-render/post-flush 队列
  适合读取已经更新的 owner 组件 DOM
watch(source, callback, { flush: 'post' })
watchEffect(effect, { flush: 'pre' })
watchSyncEffect(effect)
watchPostEffect(effect)

Vue 3.5 的 runtime-core/src/apiWatch.ts 会把 watcher job 的 PREALLOW_RECURSE、组件 uid 等信息交给 Scheduler;不应把旧版 queuePreFlushCb 代码当作当前实现。

八、cleanup 的两个层次

文章原文把多种 cleanup 混在了一起,实际至少要区分:

  1. 响应式 effect 依赖清理:Vue 3.5 的 prepareDeps() / cleanupDeps() 维护 Link,删除本轮没有重新读取的依赖边。
  2. watcher side effect cleanuponCleanup / onWatcherCleanup 用于取消请求、清理订阅等用户副作用。
  3. ReactiveEffect 的低层 cleanuponEffectCleanup() 注册的清理函数,在 effect 下一次运行前或 stop 时执行。

它们的目的不同,不能都称为“清空 effect”。

九、Scheduler 与父子组件更新

组件 render effect 的 scheduler 会把 job 放进主队列;watcher 根据 flush 进入主队列或 post 队列。Vue 3.5 Scheduler 的主要特点:

  • job 通过 QUEUED flag 去重;
  • 主队列按 id 升序执行,通常父组件 id 小于子组件;
  • pre watcher 使用 PRE flag,并在同一组件 render job 前执行;
  • post callback 会去重、排序并在主队列后执行;
  • DISPOSED job 会被跳过;
  • 开发环境会检测超过递归限制的更新;
  • 如果 flush 过程中又产生任务,会继续 drain 队列。
await nextTick()

nextTick 等待当前 flush Promise。它不等待任意 fetch、定时器或第三方 Promise。

十、target === toRaw(receiver) 与响应式原型链

Vue 的 MutableReactiveHandler.set 中有:

if (target === toRaw(receiver) && result) {
  if (!hadKey) {
    trigger(target, TriggerOpTypes.ADD, key, value)
  } else if (hasChanged(value, oldValue)) {
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
}

这个判断用于处理响应式对象作为另一个响应式对象原型时的标准 [[Set]] 传播,避免原型 proxy 和真正 receiver 各自触发一次更新。它不是“所有 Proxy 赋值都会触发两次”的规则。

可以用下面的结构理解场景:

const parent = reactive({ count: 1 })
const child = reactive({ name: 'child' })

Object.setPrototypeOf(child, parent)

effect(() => {
  console.log(child.count)
})

// 写入可能经过 child -> parent 的原型链;Vue 只在真正 raw receiver 条件满足时触发
child.count = 2

当前 RAW 分支位于 BaseReactiveHandler.get,使用 ReactiveFlags.RAW 和 reactive map,不是把字符串 "__v_raw" 写在 createSetter() 中。toRaw() 的作用是取得原始对象,不应作为普通业务 API 频繁绕过响应式代理。

十一、文章中的旧源码问题总结

以下内容保留为历史说明,但不能继续称为 Vue 3.5.39:

  • depSet<ReactiveEffect>effect.deps 是 Dep 数组:Vue 3.5 已改为 Dep + Link 链;
  • dep.wdep.ntrackOpBitmaxMarkerBits:属于旧 marker bits 实现;
  • ReactiveEffect.parent 和 parent chain guard:不是 Vue 3.5 当前字段;
  • _dirty_cacheable、独立 computed effect:属于旧 computed 实现;
  • trackEffects()triggerEffects() 直接遍历并运行 effect:不是当前主流程;
  • pendingPreFlushCbs / queuePreFlushCb:属于旧 Scheduler 结构;
  • new Proxy() 的原生示例:没有 Vue track/trigger,不能证明 Vue effect 行为;
  • effect 内反复创建 effect:会积累订阅,应使用明确的 scope/stop 管理。

十二、总结

  • Proxy 的读取建立依赖,写入/删除/集合操作根据操作类型通知相应 subscriber。
  • Vue 3.5 的依赖关系是 targetMap -> Dep -> Link -> Subscriber,不是简单的 Set 图。
  • ReactiveEffect 在运行前准备依赖、运行后清理过期 Link,并通过 batch/scheduler 控制重新执行。
  • computed 在 3.5 中自身就是 subscriber,通过 Dep version 和 globalVersion 实现惰性与稳定性优化。
  • watch 显式追踪 source,watchEffect 自动追踪同步执行期间读取的依赖。
  • onCleanuponWatcherCleanup 用于清理过期副作用,3.5 的后者不能在 await 后注册。
  • Scheduler 的 pre/job/post 时序仍可作为高层模型,但队列字段和 job flags 必须按目标 Vue 版本核对。
  • Reflect 的 receiver 影响 getter/setter 的 this;Vue 使用 Reflect 转发以保留标准对象语义。

官方参考

Vue Core v3.5.39 源码

官方文档与标准

历史源码对照

原文出处

作者:白边

原文链接:https://juejin.cn/post/7177613948907159607

来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS