Vue3 响应式系统源码解析:Map/Set Collection 响应式核心实现

本文解析 Vue3 reactivity 模块中 Collection(Map/Set/WeakMap/WeakSet) 的响应式处理逻辑,重点在设计思路与源码结构,而不是 API 使用。


1. 背景:为什么 Map/Set 要单独写代码?

Vue 对普通对象 {} 的响应式靠 get/set 即可,但对 Map/Set:

  • 方法是函数 (如 .set().get()
  • 键可能是对象
  • 有迭代器keysvaluesentriesfor...of
  • 弱集合 WeakMap/WeakSet 无法遍历

因此,Vue 必须提供一套专门的"仪表方法"(instrumentations)来接管所有操作。

这一段源码就是专门为此而设计的。


2. createIterableMethod ------ 迭代器逻辑封装

代码片段

javascript 复制代码
function createIterableMethod(method, isReadonly, isShallow) {
  return function (...args) {
    const target = this[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    const targetIsMap = isMap(rawTarget)
    const isPair = method === 'entries' || (method === Symbol.iterator && targetIsMap)
    const isKeyOnly = method === 'keys' && targetIsMap

    const innerIterator = target[method](...args)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive

    !isReadonly && track(
      rawTarget,
      TrackOpTypes.ITERATE,
      isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY,
    )

    return {
      next() {
        const { value, done } = innerIterator.next()
        return done
          ? { value, done }
          : { value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), done }
      },
      [Symbol.iterator]() {
        return this
      },
    }
  }
}

逐段解释:

① 获取原始对象
ini 复制代码
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)

Map 可能是嵌套 reactive 包装,这里确保拿到真正的原对象。

② 判断返回值是 key/value/key+value
ini 复制代码
const isPair = method === 'entries' ...
const isKeyOnly = method === 'keys'
③ 执行原生迭代器
ini 复制代码
const innerIterator = target[method](...args)
④ 依赖收集
scss 复制代码
track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)

关键点:只要调用 keys、values、entries、for-of,都要追踪依赖。

这样当集合结构变化时才能触发更新。

⑤ 迭代器包装:将每个值转成 reactive
css 复制代码
value: wrap(...)

Map 中存的值可能是对象,Vue 自动转换成 reactive 或 readonly。

这一部分是最核心的"响应式迭代器"实现。


3. createReadonlyMethod ------ 用于 readonly 集合

代码片段

typescript 复制代码
function createReadonlyMethod(type) {
  return function (...args) {
    warn(`${capitalize(type)} operation failed: target is readonly.`)
    return type === TriggerOpTypes.DELETE
      ? false
      : type === TriggerOpTypes.CLEAR ? undefined : this
  }
}

思路:

  • 任何修改(set/add/delete/clear)都会提示错误
  • 并返回合理的 fallback 值

举例:

  • delete()false
  • clear()undefined
  • set/add → 返回 this(保持链式调用)

4. createInstrumentations ------ 生成 Map/Set 全量代理方法

它会根据参数 readonlyshallow 返回不同版本的方法表。


(1)get(key)

代码片段

vbnet 复制代码
get(key) {
  const target = this[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)

  if (!readonly) {
    if (hasChanged(key, rawKey)) track(rawTarget, TrackOpTypes.GET, key)
    track(rawTarget, TrackOpTypes.GET, rawKey)
  }

  const { has } = getProto(rawTarget)

  const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive

  if (has.call(rawTarget, key)) {
    return wrap(target.get(key))
  } else if (has.call(rawTarget, rawKey)) {
    return wrap(target.get(rawKey))
  }
}

关键细节:

① key 和 rawKey 都要 track

原因:用户可能用 reactive(obj) 或 raw obj 作为 map key。

② 始终 wrap 返回值

所有 get 到的 value 必须返回 reactive/readonly 版本。


(2)size 属性

csharp 复制代码
get size() {
  const target = this[ReactiveFlags.RAW]
  !readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  return target.size
}

size 依赖整个集合结构,所以也要使用 ITERATE 依赖类型。


(3)has(key)

逻辑和 get 类似,也要对 key 与 rawKey 都进行 track。


(4)forEach

Vue 为回调参数做响应式包装:

scss 复制代码
callback.call(thisArg, wrap(value), wrap(key), observed)

确保:

  • value 是 reactive
  • key 是 reactive
  • this 是 reactive

5. Mutation(增删改清)操作

如果不是 readonly,则提供真正的 set/add/delete/clear。


(1)add (for Set)

scss 复制代码
add(value) {
  value = toRaw(value)
  const hadKey = target.has(value)
  if (!hadKey) {
    target.add(value)
    trigger(target, TriggerOpTypes.ADD, value, value)
  }
  return this
}

(2)set (for Map)

核心逻辑

  • 把 value 转 raw
  • 判断 key 是否存在
  • 触发 ADD 或 SET

(3)delete

触发 DELETE 类型依赖,并将旧值传递给 watcher。


(4)clear

触发 CLEAR,且如果是 Map 会克隆 oldTarget(仅 dev)。


6. iterator 方法统一挂载

sql 复制代码
['keys','values','entries',Symbol.iterator].forEach(method => {
  instrumentations[method] = createIterableMethod(method, readonly, shallow)
})

这是响应式迭代器的核心。


7. createInstrumentationGetter ------ Proxy.get 捕获器

它定义了所有 Collection 的代理行为。

vbnet 复制代码
get(target, key, receiver) {
  if (key === ReactiveFlags.IS_REACTIVE) return !isReadonly
  if (key === ReactiveFlags.IS_READONLY) return isReadonly
  if (key === ReactiveFlags.RAW) return target

  return Reflect.get(
    hasOwn(instrumentations, key) && key in target
      ? instrumentations
      : target,
    key,
    receiver,
  )
}

逻辑:

  1. 读标识位(isReactive/isReadonly/raw)
  2. 如果有对应的 instrumentations 方法,则从那里取
  3. 否则取集合原本的方法

8. 最终导出的四种 handler

arduino 复制代码
export const mutableCollectionHandlers
export const shallowCollectionHandlers
export const readonlyCollectionHandlers
export const shallowReadonlyCollectionHandlers

Vue 会根据:

  • reactive()
  • shallowReactive()
  • readonly()
  • shallowReadonly()

来使用不同的 handler。


总结

这段源码是 Vue3 响应式系统中最复杂的一部分之一,核心设计理念包括:

  • 以 instrumentations 表包装所有 Collection 方法
  • 迭代器统一代理,确保 for-of / keys / values 都能被追踪
  • 返回值自动 wrap 成 reactive 或 readonly
  • 同时支持 raw key 与 reactive key 的一致性检查
  • 对不同模式(shallow / readonly)进行统一抽象

它是 Vue 能优雅支持 Map/Set 响应式的关键。


本文部分内容借助 AI 辅助生成,并由作者整理审核。

相关推荐
消失的旧时光-19434 小时前
Kotlinx.serialization 对多态对象(sealed class )支持更好用
java·服务器·前端
少卿4 小时前
React Compiler 完全指南:自动化性能优化的未来
前端·javascript
广州华水科技4 小时前
水库变形监测推荐:2025年单北斗GNSS变形监测系统TOP5,助力基础设施安全
前端
广州华水科技4 小时前
北斗GNSS变形监测一体机在基础设施安全中的应用与优势
前端
七淮4 小时前
umi4暗黑模式设置
前端
8***B4 小时前
前端路由权限控制,动态路由生成
前端
军军3605 小时前
从图片到点阵:用JavaScript重现复古数码点阵艺术图
前端·javascript
znhy@1235 小时前
Vue基础知识(一)
前端·javascript·vue.js
terminal0075 小时前
浅谈useRef的使用和渲染机制
前端·react.js·面试
我的小月月5 小时前
🔥 手把手教你实现前端邮件预览功能
前端·vue.js