vue-reactivity readonly 阅读 通过 readonly 可以设置一些只读的值,
1 2 3 4 5 6 7 8 9 const original = { foo: 1 , bar: { baz: 2 } }const wrapped = readonly (original)effect(()=> { console .log(warpped) }) warpped.foo = 2
readonly 是对 createReactiveObject
的封装
1 2 3 4 5 6 7 8 9 10 export function readonly <T extends object >( target: T ): Readonly <UnwrapNestedRefs <T >> { return createReactiveObject( target, true , readonlyHandlers, readonlyCollectionHandlers ) }
readonlyHandlers
和 readonlyCollectionHandlers
readonlyHandlers
相对简单,主要是在 set
和 deleteProperty
阶段,不对 target 进行处理,而直接返回 true
另外如果是开发环境,则输出相应的的提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export const readonlyHandlers: ProxyHandler<object > = { get: readonlyGet, has, ownKeys, set(target, key) { if (__DEV__) { console .warn( `Set operation on key "${String (key)} " failed: target is readonly.` , target ) } return true }, deleteProperty(target, key) { if (__DEV__) { console .warn( `Delete operation on key "${String (key)} " failed: target is readonly.` , target ) } return true } }
而 readonlyGet
也是通过 createGetter 进行统一构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 const readonlyGet = createGetter(true )function createGetter (isReadonly = false , shallow = false ) { return function get (target: object , key: string | symbol, receiver: object ) { if (key === ReactiveFlags.isReactive) { return !isReadonly } else if (key === ReactiveFlags.isReadonly) { return isReadonly } else if ( key === ReactiveFlags.raw && receiver === (isReadonly ? (target as any ).__v_readonly : (target as any ).__v_reactive) ) { return target } const targetIsArray = isArray(target) if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect .get(arrayInstrumentations, key, receiver) } const res = Reflect .get(target, key, receiver) if ((isSymbol(key) && builtInSymbols.has(key)) || key === '__proto__' ) { return res } if (!isReadonly) { track(target, TrackOpTypes.GET, key) } if (shallow) { return res } if (isRef(res)) { return targetIsArray ? res : res.value } if (isObject(res)) { return isReadonly ? readonly (res) : reactive(res) } return res } }
readonlyCollectionHandlers
只代理 get 方法,这个因为对应 Map Set 等类型其实通过 调用对应的函数来处理数据,而不是直接 set
1 2 3 4 5 6 7 const set = new Set ()set.add(1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = { get: createInstrumentationGetter(true , false ) } function createInstrumentationGetter (isReadonly: boolean , shallow: boolean ) { const instrumentations = shallow ? shallowInstrumentations : isReadonly ? readonlyInstrumentations : mutableInstrumentations return ( target: CollectionTypes, key: string | symbol, receiver: CollectionTypes ) => { if (key === ReactiveFlags.isReactive) { return !isReadonly } else if (key === ReactiveFlags.isReadonly) { return isReadonly } else if (key === ReactiveFlags.raw) { return target } return Reflect .get( hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver ) } }
着重看 readonlyInstrumentations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const readonlyInstrumentations: Record<string , Function > = { get(this : MapTypes, key: unknown) { return get(this , key, toReadonly) }, get size() { return size((this as unknown) as IterableCollections) }, has, add: createReadonlyMethod(TriggerOpTypes.ADD), set: createReadonlyMethod(TriggerOpTypes.SET), delete : createReadonlyMethod(TriggerOpTypes.DELETE), clear: createReadonlyMethod(TriggerOpTypes.CLEAR), forEach: createForEach(true , false ) } function createReadonlyMethod (type : TriggerOpTypes ): Function { return function (this : CollectionTypes, ...args: unknown[] ) { if (__DEV__) { const key = args[0 ] ? `on key "${args[0 ]} " ` : `` console .warn( `${capitalize(type )} operation ${key} failed: target is readonly.` , toRaw(this ) ) } return type === TriggerOpTypes.DELETE ? false : this } }
这里可以看到
通过工厂函数统一对涉及 写的函数进行覆盖
其他读函数如 size
has
get
则和正常代理逻辑一致
总结 readonly 是 vue-reactivity 中比较简单的一环,总结下来以下一些要点
设置 readonly 的原理是 proxy 代理 set 方法,不对 target 做处理,直接返回
proxy 代理 get 区分 一般类型,和一些特殊的集合类型
如果是一般类型则对 value 递归 readonly 保证不可写
特殊set map 等集合类型,因为这些类型的写方法也在 get 操作里,所以需要对相应的方法进行代理覆盖,同时没有 set 相关代理逻辑