TypeScript如何从含Map/Record属性的类返回指定类型值
实现类型安全的泛型DataStore方法
原写法的问题根源
- 泛型
T extends keyof IDataStore虽然约束了storeKey的取值范围,但TypeScript在方法内部无法建立「storeKey取值」和「对应Map存储值类型」的强关联,会把this[storeKey]推断为Map<TUUID, IUser> | Map<TUUID, IProduct>联合类型 - 联合类型的
set方法参数会被推导为所有值类型的交集,IUser & IProduct因为type属性冲突直接被判定为never,所以传参会抛出类型错误 get方法的返回值也会被推导为所有值类型的联合,无法根据传入的storeKey自动缩窄到对应实体类型,访问专属属性会报错
正确实现方案
首先定义存储键和实体类型的映射关系,再通过泛型绑定两者的对应关系,内部仅做最小范围的安全类型断言,全程不会出现any类型:
type TUUID = string; interface IUser { name: string; type: 'customer' | 'client'; } interface IProduct { name: string; count: number; type: 'electronics' | 'food' | 'drinks'; } // 定义存储键到对应值类型的映射表,作为泛型推导的基础 interface IStoreEntityMap { users: IUser; products: IProduct; } interface IDataStore { users: Map<TUUID, IStoreEntityMap['users']>; products: Map<TUUID, IStoreEntityMap['products']>; } export class DataStore implements IDataStore { users: IDataStore['users'] = new Map(); products: IDataStore['products'] = new Map(); getEntity<T extends keyof IStoreEntityMap>( storeKey: T, id: TUUID ): IStoreEntityMap[T] | null { const targetMap = this[storeKey] as Map<TUUID, IStoreEntityMap[T]>; return targetMap.has(id) ? targetMap.get(id)! : null; } updateEntity<T extends keyof IStoreEntityMap>( storeKey: T, id: TUUID, data: IStoreEntityMap[T] ): IStoreEntityMap[T] | null { const targetMap = this[storeKey] as Map<TUUID, IStoreEntityMap[T]>; targetMap.set(id, data); return targetMap.get(id) ?? null; } } // 类型推导测试 const store = new DataStore(); const user = store.getEntity('users', '1234'); // 自动推导为 IUser | null const product = store.getEntity('products', 'abcd'); // 自动推导为 IProduct | null // 类型缩窄后可直接访问专属属性,无编译错误 if (product) { console.log(product.count); } const _user: IUser = { name: 'John Doe', type: 'customer' }; const updatedUser = store.updateEntity('users', '1234', _user); // 自动推导为 IUser | null // 传错类型会被TS直接拦截,以下代码会抛出编译错误 // store.updateEntity('users', '1234', { name: 'Coke', count: 100, type: 'drinks' });
实现说明
- 新增
IStoreEntityMap作为类型映射表,明确每个存储键对应的实体类型,泛型推导完全基于这个映射表,不会出现类型联合的问题 - 方法内部仅将
this[storeKey]断言为对应泛型的Map类型,这个断言是完全类型安全的,因为IDataStore的定义已经强制约束了键和对应Map类型的匹配关系 - 显式声明方法返回值类型,避免TS默认推导成联合类型
updateEntity的data参数直接使用映射类型IStoreEntityMap[T],不需要通过ReturnType获取Map.get的返回值,从根源避免undefined类型混入参数导致的校验错误- 不需要编写方法重载,也不会出现方法内部参数为
any的问题,所有入参、出参都有强类型校验,传错类型会在编译阶段直接拦截
内容的提问来源于stack exchange,提问作者Provash
相关产品推荐
相关产品推荐

