TypeScript将泛型对象数组转为对象集合时如何正确保留类型
解决方案
调整说明
需要同时修改库侧代码,配合少量应用层调整即可满足需求,核心问题在于原泛型设计没有建立配置name字面量和对应Entity类型的映射关系,且没有收窄配置数组的字面量类型。
库代码修改
基础类型调整
// 为WrapperConfig增加Name泛型参数存储名字面量类型 export type WrapperConfig<Name extends string, Entity> = { name: Name } type Wrapper<Entity> = { name: string, data: Entity // .. 其他原有属性 }
createWrapper 调整
function createWrapper<Name extends string, Entity>(wrapperConfig: WrapperConfig<Name, Entity>): Wrapper<Entity> { // 原有实现逻辑不变 return { name: wrapperConfig.name, data: {} as Entity } as Wrapper<Entity> }
createManyWrappers 重写
// 使用const泛型自动收窄传入配置数组的字面量类型,遍历映射生成返回值类型 export function createManyWrappers<const T extends WrapperConfig<string, any>[]>(wrapperConfigs: T) { const wrappers = {} as { // 键重映射生成带Wrapper后缀的属性名,同时提取对应配置的Entity类型 [K in T[number] as `${K['name']}Wrapper`]: Wrapper<K extends WrapperConfig<any, infer E> ? E : never> }; for (const wrapperConfig of wrapperConfigs) { const nameWithSuffix = `${wrapperConfig.name}Wrapper` as keyof typeof wrappers; wrappers[nameWithSuffix] = createWrapper(wrapperConfig); } return wrappers; }
如果需要兼容TS4.5以下版本,去掉泛型前的const修饰,在应用层传入配置时加as const即可。
应用层调整
无需大量修改,只需保证配置的name类型为字面量即可,两种常用写法:
写法1:直接传入配置字面量(改动最小)
type Entity1 = { foo: string }; type Entity2 = { bar: number }; type Entity3 = { cor: number }; const wrappers = createManyWrappers([ { name: 'entity1' } as WrapperConfig<'entity1', Entity1>, { name: 'entity2' } as WrapperConfig<'entity2', Entity2>, { name: 'entity3' } as WrapperConfig<'entity3', Entity3> ]); // 现在属性可自动补全提示 const { entity1Wrapper, entity2Wrapper, entity3Wrapper } = wrappers; const data1 = entity1Wrapper.data // 类型为 Entity1 const data2 = entity2Wrapper.data // 类型为 Entity2 const data3 = entity3Wrapper.data // 类型为 Entity3
写法2:提前定义配置数组(加as const收窄类型)
const wrapperConfigs = [ { name: 'entity1' } as WrapperConfig<'entity1', Entity1>, { name: 'entity2' } as WrapperConfig<'entity2', Entity2>, { name: 'entity3' } as WrapperConfig<'entity3', Entity3> ] as const; // 必须加as const收窄为tuple类型,否则name会被拓宽为string const wrappers = createManyWrappers(wrapperConfigs);
可选简化写法(库侧可加工具函数)
如果要简化应用层类型声明,可在库侧增加配置定义工具函数:
// 库侧新增 export function defineConfig<Name extends string, Entity>(name: Name, entitySample?: Entity): WrapperConfig<Name, Entity> { return { name } as WrapperConfig<Name, Entity>; }
应用层使用更简洁:
const wrappers = createManyWrappers([ defineConfig('entity1', {} as Entity1), defineConfig('entity2', {} as Entity2), defineConfig('entity3', {} as Entity3) ]);
内容的提问来源于stack exchange,提问作者mdrobny
相关产品推荐
相关产品推荐

