通过关联参数类型区分TypeScript联合类型的实现问题
TypeScript TS7053错误解决:泛型关联枚举与类型
问题背景
先定义了以下枚举、接口与类型:
enum TableNames { clients = "clients", products = "products" } interface IClient { client: string; location: string; type: string; } interface IProduct { product: string; family: string; subfamily: string; } type TSelector<T> = { [K in keyof T]: T[K][]; }; type TClientSelector = TSelector<IClient>; type TProductSelector = TSelector<IProduct>;
基于上述类型实现了初始化类和工厂函数:
export class BaseClientSelector implements TClientSelector { client: string[] = []; type: string[] = []; location: string[] = []; } export class BaseProductSelector implements TProductSelector { product: string[] = []; family: string[] = []; subfamily: string[] = []; } const getEmptySelectorObject = (name: TableNames): TClientSelector | TProductSelector => { if (name === TableNames.clients) { return new BaseClientSelector(); } else { return new BaseProductSelector(); } };
在实现getSelectorData时遇到TS7053错误:
export const getSelectorData = ( arrayOfObj: (IClient | IProduct)[], name: TableNames ): TClientSelector | TProductSelector => { const selectorData: TClientSelector | TProductSelector = getEmptySelectorObject(name); for (const col in selectorData) { selectorData[col] = Array.from( // ^^^^^^^^^^^^^^^^^ TS7053: 元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "TClientSelector | TProductSelector" new Set(arrayOfObj.map((obj: IClient | IProduct) => obj[col])) // ^^^^^^^^ TS7053: 元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "IClient | IProduct" ).sort((a, b) => String(a).localeCompare(String(b))); } return selectorData; };
问题原因
TypeScript无法建立name参数与selectorData具体类型的关联关系:
- 遍历
selectorData得到的col被推断为string类型,无法直接索引联合类型TClientSelector | TProductSelector arrayOfObj是联合类型数组,用string类型的col索引时,TypeScript无法确认该键在所有联合成员中存在
尝试过类型守卫会导致代码重复,用any会丢失类型安全,函数重载灵活性不足,需要更优雅的泛型方案。
泛型解决方案
通过创建枚举到实体类型的映射,结合泛型让TypeScript自动推断类型关联:
1. 定义枚举与类型的映射
// 建立TableNames到对应实体类型的映射 type TableToEntity = { [TableNames.clients]: IClient; [TableNames.products]: IProduct; }; // 根据枚举类型推导对应的选择器类型 type TableToSelector<T extends TableNames> = TSelector<TableToEntity[T]>;
2. 泛化工厂函数
修改getEmptySelectorObject为泛型函数,让返回类型与输入的枚举值关联:
const getEmptySelectorObject = <T extends TableNames>(name: T): TableToSelector<T> => { if (name === TableNames.clients) { return new BaseClientSelector() as TableToSelector<T>; } else { return new BaseProductSelector() as TableToSelector<T>; } };
3. 实现类型安全的getSelectorData
用泛型约束参数之间的类型关系,解决索引问题:
export const getSelectorData = <T extends TableNames>( arrayOfObj: TableToEntity[T][], name: T ): TableToSelector<T> => { const selectorData = getEmptySelectorObject(name); // 将Object.keys断言为当前实体类型的键数组,避免string类型问题 (Object.keys(selectorData) as Array<keyof TableToEntity[T]>).forEach(col => { selectorData[col] = Array.from( new Set(arrayOfObj.map(obj => obj[col])) ).sort((a, b) => String(a).localeCompare(String(b))); }); return selectorData; };
方案优势
- 保持了完全的类型安全,没有使用
any - 避免了类型守卫带来的代码重复
- 泛型自动推断类型关联,调用时会根据传入的
name自动约束arrayOfObj的类型,返回对应类型的选择器对象
内容的提问来源于stack exchange,提问作者DanMacGom
相关产品推荐
相关产品推荐

