实现只读代理
约 2064 字大约 7 分钟
2026-03-17
分开缓存
reactive和readonly只读代理本质上也是一个代理,只不过在写入和删除阶段会被拦截下来。因此需要把 读取 和 写入 分开进行处理。因此同一个原始对象现在不只会生成一种代理,而是可能同时存在 可响应代理 和 只读代理
关键点
- 创建两个不同的缓存表:
reactiveMap和readonlyMap,分别用于缓存可响应代理和只读代理 - 将创建代理对象的过程抽离成一个
createReactiveObject工厂函数,通过参数isReadonly区分是创建可响应代理还是只读代理
代码
reactive.ts// ... export const reactiveMap = new WeakMap<Target, any>() export const readonlyMap = new WeakMap<Target, any>() function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, ) { if (!isObject(target)) { return target } // 根据 isReadonly 参数选择使用哪个缓存表,并检查是否已经存在代理对象 // 如果存在则直接返回,否则创建新的代理对象并缓存 const proxyMap = isReadonly ? readonlyMap : reactiveMap const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } const proxy = new Proxy(target, baseHandlers) proxyMap.set(target, proxy) return proxy } export function reactive(target: unknown) { // 如果传进来的已经是 readonly 代理,就直接返回 if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target } return createReactiveObject(target as Target, false, mutableHandlers) } // ...- 创建两个不同的缓存表:
在
getter中区分只读和响应式读取在
getter函数中,不仅要返回属性值,还要识别当前是不是只读代理,并且在读取深层对象时继续把结果包装成只读对象关键点
- 创建
createGetter工厂函数,通过参数isReadonly区分是只读代理还是响应式代理 - 若想要访问原始对象,则需额外判断
receiver来自于readonlyMap还是reactiveMap中的代理 readonly对象本身就不允许被修改,因此不需要进行依赖收集- 处理深层递归时也需要额外根据
isReadonly参数判断是否需要对深层属性进行递归包装
代码
baseHandlers.tsimport { ReactiveFlags } from "./reactive"; function createGetter(isReadonly = false, shallow = false) { return function get( target: object, key: string | symbol, receiver: object, ): any { // 是否已经是代理对象 if (key === ReactiveFlags.IS_REACTIVE) { return true } // 是否是只读代理 else if (key === ReactiveFlags.IS_READONLY) { return isReadonly } // 1.前这个原始对象对应的那层代理对象自己访问 RAW 时,才把原始对象返回出去 // 2.如果现在是只读代理,就看 receiver 是不是 readonlyMap 里缓存的那个只读代理 // 反之是不是 reactiveMap 中的代理 else if ( key === ReactiveFlags.RAW && receiver === (isReadonly ? readonlyMap.get(target) : reactiveMap.get(target)) ) { return target } if (!isReadonly) { track(target, TrackOpTypes.GET, key) } const result = Reflect.get(target, key, receiver) if (isObject(result)) { return isReadonly ? readonly(result) : reactive(result) } return result } }reactive.tsexport const enum ReactiveFlags { IS_REACTIVE = "__v_isReactive", IS_READONLY = "__v_isReadonly", IS_SHALLOW = "__v_isShallow", RAW = "__v_raw", SKIP = "__v_skip", } export interface Target { [ReactiveFlags.IS_REACTIVE]?: boolean; [ReactiveFlags.IS_READONLY]?: boolean; [ReactiveFlags.IS_SHALLOW]?: boolean; [ReactiveFlags.RAW]?: any; [ReactiveFlags.SKIP]?: boolean; }- 创建
组合基础拦截器
这一步将 handler 工厂函数组合出几组可直接复用的基础拦截器
代码
baseHandlers.ts// ... const get = /*#__PURE__*/ createGetter(); const set = /*#__PURE__*/ createSetter(); const readonlyGet = /*#__PURE__*/ createGetter(true); function createSetter() { return function set( target: Record<string | symbol, unknown>, key: string | symbol, value: unknown, receiver: object, ) { const hadKey = Object.prototype.hasOwnProperty.call(target, key); const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD; const oldValue = target[key]; const oldLength = isArray(target) ? target.length : 0; const result = Reflect.set(target, key, value, receiver); if (!result) { return result; } const newLength = isArray(target) ? target.length : 0; if (hasChanged(value, oldValue) || type === TriggerOpTypes.ADD) { trigger(target, type, key); if (isArray(target) && oldLength !== newLength) { if (key !== "length") { trigger(target, TriggerOpTypes.SET, "key"); } else { for (let i = newLength; i < oldLength; i++) { trigger(target, TriggerOpTypes.DELETE, i); } } } } return result; }; } export const mutableHandlers: ProxyHandler<object> = { get, set, has, ownKeys, deleteProperty, } // ...拦截写入和删除
关键点
- 通过定义
readonlyHandlers拦截器,专门处理readonly对象的写入和删除操作 - 读取阶段依然沿用
getter拦截
代码
baseHandlers.tsexport const readonlyHandlers: ProxyHandler<object> = { get: readonlyGet, set: (_target, _key) => { console.warn( `Set operation on key ${String(_key)} failed: target is readonly.`, ) return true }, deleteProperty: (_target, _key) => { console.warn( `Delete operation on key ${String(_key)} failed: target is readonly.`, ) return true }, }- 通过定义
readonly函数除了运行时的只读限制以外,类型层也需要同步补上只读约束,让对象及其深层属性在 TypeScript 中都具备对应的只读语义
关键点
DeepReadonly的核心就是用映射类型遍历对象属性;如果当前属性的值仍然是对象,就继续递归套用DeepReadonly,否则直接保留原值类型代码
reactive.tstype DeepReadonly<T extends Record<string, any>> = T extends any ? { readonly [K in keyof T]: T[K] extends Record<string, any> ? DeepReadonly<T[K]> : T[K] } : never export function readonly<T extends object>(target: T): DeepReadonly<T> export function readonly<T>(target: T): T export function readonly(target: unknown) { return createReactiveObject(target as Target, true, readonlyHandlers); }完整代码
代码
reactive.tsimport { mutableHandlers, readonlyHandlers } from "./baseHandlers"; import { isObject } from "./utils"; export const enum ReactiveFlags { IS_REACTIVE = "__v_isReactive", IS_READONLY = "__v_isReadonly", IS_SHALLOW = "__v_isShallow", RAW = "__v_raw", SKIP = "__v_skip", } export interface Target { [ReactiveFlags.IS_REACTIVE]?: boolean; [ReactiveFlags.IS_READONLY]?: boolean; [ReactiveFlags.IS_SHALLOW]?: boolean; [ReactiveFlags.RAW]?: any; [ReactiveFlags.SKIP]?: boolean; } // 为了区分普通代理 reactive 和 readonly,将其分开进行处理 export const reactiveMap = new WeakMap<Target, any>(); export const readonlyMap = new WeakMap<Target, any>(); function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, ) { if (!isObject(target)) { return target; } const proxyMap = isReadonly ? readonlyMap : reactiveMap; const existingProxy = proxyMap.get(target); if (existingProxy) { return existingProxy; } if (target[ReactiveFlags.RAW] && target[ReactiveFlags.IS_REACTIVE]) { return target; } const proxy = new Proxy(target, baseHandlers); proxyMap.set(target, proxy); return proxy; } export function reactive<T extends object>(target: T): T; export function reactive<T>(target: T): T; export function reactive(target: unknown) { // 处理一个已经是只读的代理对象 if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target; } return createReactiveObject(target as Target, false, mutableHandlers); } type DeepReadonly<T extends Record<string, any>> = // 递归地将对象的所有属性设置为只读 // readonly [K in keyof T]: T[K] extends Record<string, any> // ? DeepReadonly<T[K]> // : T[K]; // // 另一种写法 T extends any ? { readonly [K in keyof T]: T[K] extends Record<string, any> ? DeepReadonly<T[K]> : T[K]; } : never; export function readonly<T extends object>(target: T): DeepReadonly<T>; export function readonly<T>(target: T): T; export function readonly(target: unknown) { return createReactiveObject(target as Target, true, readonlyHandlers); } export function toRaw<T>(observed: T): T { const raw = (observed as Target)[ReactiveFlags.RAW] || observed; return raw === observed ? raw : toRaw(raw); }baseHandlers.tsimport { ReactiveFlags, reactiveMap, readonlyMap, toRaw, readonly, } from "./reactive"; import { enableTracking, pauseTracking, track, trigger } from "./effect"; import { TrackOpTypes, TriggerOpTypes } from "./operations"; import { isObject, hasChanged, isArray } from "./utils"; import { reactive } from "./reactive"; export const ITERATE_KEY = Symbol("iterate"); const arrayInstrumentationsRecord: Record<string, Function> = {}; (["includes", "indexOf", "lastIndexOf"] as const).forEach((key) => { const methods = Array.prototype[key] as any; arrayInstrumentationsRecord[key] = function ( this: unknown[], ...args: unknown[] ) { const raw = toRaw(this); for (let i = 0; i < raw.length; i++) { track(raw, TrackOpTypes.GET, i); } const res = methods.apply(raw, args); if (res === -1 || res === false) { return methods.apply(raw, args.map(toRaw)); } return res; }; }); (["push", "pop", "shift", "unshift", "splice"] as const).forEach((key) => { const method = Array.prototype[key] as any; arrayInstrumentationsRecord[key] = function ( this: unknown, ...args: unknown[] ) { pauseTracking(); const res = method.apply(this, args); enableTracking(); return res; }; }); const get = /*#__PURE__*/ createGetter(); const set = /*#__PURE__*/ createSetter(); const readonlyGet = /*#__PURE__*/ createGetter(true); function createGetter(isReadonly = false, shallow = false) { return function get( target: object, key: string | symbol, receiver: object, ): any { if (key === ReactiveFlags.IS_REACTIVE) { return true; } else if (key === ReactiveFlags.IS_READONLY) { return isReadonly; } else if ( key === ReactiveFlags.RAW && receiver === (isReadonly ? readonlyMap.get(target) : reactiveMap.get(target)) ) { return target; } const targetArray = isArray(target); if (targetArray && key in arrayInstrumentationsRecord) { return Reflect.get(arrayInstrumentationsRecord, key, target); } // 如果是只读情况,则不会进行依赖收集 if (!isReadonly) { track(target, TrackOpTypes.GET, key); } const result = Reflect.get(target, key, receiver); if (isObject(result)) { return isReadonly ? readonly(result) : reactive(result); } return result; }; } function set( target: Record<string | symbol, unknown>, key: string | symbol, value: unknown, receiver: object, ): boolean { // 判断 target 中是否存在这个 key 如果存在则表示是修改操作,否则是添加操作 // 且如果是修改操作,那么还要额外判断两次操作的值是否相同 const hadKey = Object.prototype.hasOwnProperty.call(target, key); const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD; const oldValue = target[key]; // 修改之前的数组长度 const oldLength = isArray(target) ? target.length : 0; const result = Reflect.set(target, key, value, receiver); if (!result) { return result; } // 修改之后的长度 const newLength = isArray(target) ? target.length : 0; // 如果 hasChanged 是 false 那么证明两个值不一样,那就是 set // 如果 hasChanged 是 true 说明两个值变了也就是 add if (hasChanged(value, oldValue) || type === TriggerOpTypes.ADD) { trigger(target, type, key); if (isArray(target) && oldLength !== newLength) { // 非显示 .length 修改数组长度 if (key !== "length") { trigger(target, TriggerOpTypes.SET, "key"); } else { // 如果 newLength 小于 oldLength 则进入循环,也就意味着删除数组后续元素 for (let i = newLength; i < oldLength; i++) { trigger(target, TriggerOpTypes.DELETE, i); } } } } return result; } function has(target: object, key: string | symbol): boolean { track(target, TrackOpTypes.HAS, key); return Reflect.has(target, key); } function ownKeys(target: object): (string | symbol)[] { track(target, TrackOpTypes.ITERATE, ITERATE_KEY); return Reflect.ownKeys(target); } function deleteProperty( target: Record<string | symbol, unknown>, key: string | symbol, ): boolean { const hadKey = Object.prototype.hasOwnProperty.call(target, key); const result = Reflect.deleteProperty(target, key); if (hadKey && result) { trigger(target, TriggerOpTypes.DELETE, key); } return result; } export const mutableHandlers: ProxyHandler<object> = { get, set, has, ownKeys, deleteProperty, }; export const readonlyHandlers: ProxyHandler<object> = { get: readonlyGet, set: (_target, _key) => { console.warn( `Set operation on key ${String(_key)} failed: target is readonly.`, ); return true; }, deleteProperty: (_target, _key) => { console.warn( `Delete operation on key ${String(_key)} failed: target is readonly.`, ); return true; }, };
