1217 字
6 分钟
Vue 3 的四种响应式代理

四个 API 出自同一个函数#

reactive、shallowReactive、readonly、shallowReadonly 的返回值形态不同,但源码里都走同一个入口:createReactiveObject,位于 packages/reactivity/src/reactive.ts。

packages/reactivity/src/reactive.ts
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// …省略前面的类型检查与标记判断
// 同一个对象重复代理时直接复用缓存
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const targetType = targetTypeMap(toRawType(target))
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}

四个 API 的区别,只是往这个函数里传了不同的 handler 和不同的 proxyMap。

底层是 Proxy#

Vue 3 的响应式只建立在 ES6 Proxy 上。get 里收集依赖,set 里触发更新。下面是简化示意:

new Proxy(target, {
get(target, key) {
track(target, key) // 读取属性时收集依赖
return Reflect.get(target, key)
},
set(target, key, val) {
const result = Reflect.set(target, key, val)
trigger(target, key) // 修改属性时触发更新
return result
}
})

原始类型想获得响应式,只能先包进对象,交给 ref 处理。

差异只有三个维度#

  • isReadonly:是否只读
  • handler:传 mutableHandlers 还是 readonlyHandlers,以及是否用浅层版本
  • proxyMap:用哪个缓存表

下面四节分别看这四个 API。

reactive:深度响应式#

返回一个深度响应式代理,嵌套对象会自动递归转成响应式。

packages/reactivity/src/reactive.ts
export function reactive<T extends object>(target: T): Reactive<T>
export function reactive(target: object) {
// 对只读代理调用 reactive,直接返回它本身
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}

mutableHandlers 的行为:

  • get 时自动递归 reactive 子对象,并调用 track 收集依赖
  • set 时调用 trigger 触发更新
  • 所有层级都被监听
const person = reactive({
name: 'zs',
age: 18,
friend: { name: 'ls' }
})
person.name = 'ww' // 触发更新
person.friend.name = 'zl' // 也触发,嵌套对象已被递归代理

shallowReactive:只有第一层响应式#

只有第一层属性是响应式的,深层对象不代理、不递归。

packages/reactivity/src/reactive.ts
export function shallowReactive<T extends object>(
target: T,
): ShallowReactive<T> {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap,
)
}

它沿用同一套 get 逻辑,只是在收集完依赖之后提前返回,不做递归代理。真实源码里的这两行就是全部差别:

packages/reactivity/src/baseHandlers.ts
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (isShallow) {
return res
}

所以第一层的读取照样会被追踪,只有嵌套对象不再被转换。

const person = shallowReactive({
name: 'zs',
friend: { name: 'ls' }
})
person.name = 'ww' // 触发更新
person.friend.name = 'zl' // 不触发,深层不是 Proxy

另外一点和 reactive 不同:shallowReactive 不会自动解包 ref,根级属性上的 ref 需要写 .value。

readonly:深度只读#

返回一个深度只读代理,任何层级都只能读。

packages/reactivity/src/reactive.ts
export function readonly<T extends object>(
target: T,
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers,
readonlyMap,
)
}

readonlyHandlers 拒绝写入。真实源码里警告只在开发环境发出:

packages/reactivity/src/baseHandlers.ts
set(target: object, key: string | symbol) {
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target,
)
}
return true
}

deleteProperty 同样处理:开发环境报警告,然后返回 true。

const state = readonly({
name: 'zs',
friend: { name: 'ls' }
})
state.name = 'ww' // 警告,不生效
state.friend.name = 'zl' // 警告,不生效,深层同样只读

组件的 props 也是只读的。官方要求单向数据流,在子组件里改 props 会在控制台收到告警。

不过要注意别把这句理解成「props 是 readonly() 的返回值」。源码里 instance.props 拿到的是 shallowReactive(props),对外暴露的 $props 才会在开发环境被 shallowReadonly 再包一层,写入告警也来自这里。

「只读」指的是不能改这条约束,而不是说 props 由 readonly() 创建。

shallowReadonly:只有第一层只读#

第一层只读,深层可以修改,而且深层不是响应式。

packages/reactivity/src/reactive.ts
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
return createReactiveObject(
target,
true,
shallowReadonlyHandlers,
shallowReadonlyCollectionHandlers,
shallowReadonlyMap,
)
}
  • 第一层:set 被拦截,只读
  • 深层:直接返回原对象,不代理,可以修改
const state = shallowReadonly({
name: 'zs',
friend: { name: 'ls' }
})
state.name = 'ww' // 禁止,发出警告
state.friend.name = 'zl' // 允许,且不触发更新

它同样不会自动解包 ref。

四个 API 对照#

API可写范围代理范围
reactive全部层级可写全部层级,嵌套对象递归代理
shallowReactive全部层级可写,深层修改不触发更新仅第一层
readonly全部层级只读,写入在开发环境告警全部层级
shallowReadonly仅第一层只读仅第一层

第 4 篇,共 19 篇本文属于系列

Vue 3 的四种响应式代理
https://www.choria.top/posts/vue3-reactive/
作者
乔瑞雅·克林威尔
发布于
2026-04-08
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 214 天

部分内容可能已过时