如何泛型地将TypeScript Map转换为Readonly Map?
实现包含泛型Map的类的只读副本
要实现类的只读副本,同时把内部的Map转为ReadonlyMap并保留泛型类型,TypeScript完全支持这个需求。核心是自定义一个深度只读工具类型,专门处理Map到ReadonlyMap的转换,同时递归处理其他属性。
步骤1:自定义深度只读工具类型
用条件类型判断目标类型,如果是Map<K, V>就转为对应的ReadonlyMap<K, V>,否则递归处理对象属性或直接返回原始类型:
type DeepReadonly<T> = // 处理Map,提取泛型K和V,转为ReadonlyMap T extends Map<infer K, infer V> ? ReadonlyMap<K, DeepReadonly<V>> : // 处理普通对象,递归转为只读属性 T extends object ? { readonly [P in keyof T]: DeepReadonly<T[P]> } : // 原始类型直接返回 T;
步骤2:在类中实现copy()方法
在你的类里,让copy()方法返回DeepReadonly<你的类>类型的实例。运行时只需返回一个副本(类型断言即可,因为运行时行为不重要):
class DataStore<K, V> { private content: Map<K, V>; public label: string; constructor(label: string, initialData: Map<K, V>) { this.label = label; this.content = new Map(initialData); } // 返回深度只读的副本 copy(): DeepReadonly<DataStore<K, V>> { return new DataStore(this.label, this.content) as DeepReadonly<DataStore<K, V>>; } // 示例修改方法 addEntry(key: K, value: V) { this.content.set(key, value); } }
效果验证
使用时,只读副本的属性和内部Map都会被TypeScript标记为只读,触发编译错误:
const store = new DataStore("demo", new Map([["id", 123]])); const readonlyStore = store.copy(); // ❌ 编译错误:label是只读属性 readonlyStore.label = "new label"; // ❌ 编译错误:ReadonlyMap没有set方法 readonlyStore.content.set("name", "Alice"); // ✅ 可以正常读取 console.log(readonlyStore.content.get("id"));
进阶:隐藏修改方法(可选)
如果希望只读副本完全暴露只读操作,可以定义一个只读接口,让copy()返回该接口类型:
interface ReadonlyDataStore<K, V> { readonly label: string; readonly content: ReadonlyMap<K, V>; getEntry(key: K): V | undefined; } class DataStore<K, V> implements ReadonlyDataStore<K, V> { private content: Map<K, V>; public readonly label: string; constructor(label: string, initialData: Map<K, V>) { this.label = label; this.content = new Map(initialData); } copy(): ReadonlyDataStore<K, V> { return new DataStore(this.label, this.content); } getEntry(key: K): V | undefined { return this.content.get(key); } addEntry(key: K, value: V) { this.content.set(key, value); } }
此时readonlyStore只能调用getEntry,无法访问addEntry,进一步强化不可变性。
内容的提问来源于stack exchange,提问作者petroni
相关产品推荐
相关产品推荐

