vue3 源代码reactive 到底干了什么?
packages\reactivity\src\reactive.ts
在 reactive.ts 里搜索 export function reactive(大概在 80 行左右)。
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (__DEV__) {
warn(
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
target,
)}`,
)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
我把它拆解为 5 个关键步骤。
第一步:身份检查(只加工对象)
if (!isObject(target)) {
if (__DEV__) {
warn(`value cannot be made ...`)
}
return target
}
大白话:Proxy 只能代理“对象”(Object、Array、Map、Set 等)。如果你传个数字 123 或字符串 "hello" 进来,Vue 直接无视你,把原值返回,并在开发环境下弹个警告。
这就是为什么:如果你想让一个基本类型变成响应式,得用 ref 而不是 reactive。
第二步:防止重复加工(已经是 Proxy 了吗?)
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
大白话:如果你拿一个已经是 Proxy 的东西再丢给 reactive(),它会直接还给你。
细节:target[ReactiveFlags.RAW] 是一个特殊的标记。如果 target 是 Proxy,它能识别这个标记并返回原对象。
例外:有一种情况允许重复加工——把一个响应式对象变成“只读”对象(即 readonly(reactive(obj)) 是允许的)。
第三步:黑名单检查(它能被加工吗?)
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
大白话:有些对象是 Vue 不想触碰的。
谁在黑名单里?
被 Object.freeze() 冻结的对象。
特殊的内置对象(如 RegExp, Date, Promise)。
带有 __v_skip: true 标记的对象(你可以手动让某个大对象跳过响应式)。
结论:如果是在黑名单里的对象,原样返回。
第四步:查缓存(别重复造轮子)
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
大白话:Vue 内部维护了一张表(proxyMap,是一个 WeakMap)。
场景:
const obj = { name: 'vue' }
const p1 = reactive(obj)
const p2 = reactive(obj) // 再次调用
当第二次调用 reactive(obj) 时,Vue 发现 obj 已经在表里了,直接把上次造好的 p1 返回给你。这样能保证 p1 === p2,节省性能。
第五步:正式合体(最关键的一步)
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
大白话:这是真正干活的地方。
new Proxy(target, handlers):
target: 原始对象。
handlers: 这是响应式的灵魂。它定义了当你“读取”或“修改”对象时,要做什么操作(比如追踪依赖、触发更新)。
分支逻辑:
如果是 Map, Set, WeakMap, WeakSet,使用 collectionHandlers。
如果是 普通对象或数组,使用 baseHandlers(也就是你在入口看到的 mutableHandlers)。
最后:把新生成的 proxy 存入缓存表,并返回它。
总结:reactive 到底干了啥? 你只需要记住这个流程图:
传个东西进来 -> 是对象吗?(不是就滚粗)
看一眼 -> 已经是 Proxy 了吗?(是就直接还你)
再看一眼 -> 在黑名单里吗?(是就原样返回)
查查表 -> 以前加工过吗?(加工过就把旧的给你)
动手 -> 披上一层 Proxy 的外套(注入 handlers),存进表里,收工!