
四个 API 出自同一个函数
reactive、shallowReactive、readonly、shallowReadonly 的返回值形态不同,但源码里都走同一个入口:createReactiveObject,位于 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:深度响应式
返回一个深度响应式代理,嵌套对象会自动递归转成响应式。
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:只有第一层响应式
只有第一层属性是响应式的,深层对象不代理、不递归。
export function shallowReactive<T extends object>( target: T,): ShallowReactive<T> { return createReactiveObject( target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap, )}它沿用同一套 get 逻辑,只是在收集完依赖之后提前返回,不做递归代理。真实源码里的这两行就是全部差别:
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:深度只读
返回一个深度只读代理,任何层级都只能读。
export function readonly<T extends object>( target: T,): DeepReadonly<UnwrapNestedRefs<T>> { return createReactiveObject( target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap, )}readonlyHandlers 拒绝写入。真实源码里警告只在开发环境发出:
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:只有第一层只读
第一层只读,深层可以修改,而且深层不是响应式。
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 | 仅第一层只读 | 仅第一层 |
Vue 3 学习笔记
连载中分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 214 天
部分内容可能已过时