如何用TypeScript泛型实现多类型列表项的通用列表组件
解决TypeScript泛型在通用表格行组件中的类型匹配问题
你的问题核心在于TypeScript无法推断出Row组件和当前item的类型是严格匹配的——原来的getRowComponentByType返回的是联合类型组件,TypeScript会默认要求data属性同时满足所有联合类型的交集(也就是Cat & Dog),这显然不符合实际场景。
下面是重构后的完整解决方案,通过类型映射让TypeScript能精准关联实体类型和对应的行组件:
1. 定义类型与组件的映射关系
首先明确所有实体类型和其__typename的对应关系,同时建立组件映射表:
import React from 'react'; // 假设你的实体类型定义如下(根据实际情况调整) type Cat = { __typename: 'Cat'; id: string; name: string; meowVolume: number; }; type Dog = { __typename: 'Dog'; id: string; name: string; barkVolume: number; }; // 1. 定义实体类型映射表:将__typename和对应的实体类型绑定 type EntityTypeMap = { Cat: Cat; Dog: Dog; // 后续新增的实体类型都在这里添加 }; // 2. 提取所有合法的__typename类型 type EntityTypeName = keyof EntityTypeMap; // 3. 定义行组件的通用类型 type RowComponent<K extends EntityTypeName> = React.FC<{ data: EntityTypeMap[K] }>; // 4. 实现具体的行组件 const CatRow: RowComponent<'Cat'> = ({ data }) => ( <tr> <td>{data.name}</td> <td>{data.meowVolume}</td> </tr> ); const DogRow: RowComponent<'Dog'> = ({ data }) => ( <tr> <td>{data.name}</td> <td>{data.barkVolume}</td> </tr> ); // 5. 组件映射表:__typename -> 对应的行组件 const rowComponentMap: { [K in EntityTypeName]: RowComponent<K> } = { Cat: CatRow, Dog: DogRow, };
2. 实现类型安全的组件获取函数
替换原来的getRowComponentByType,让TypeScript能根据输入的__typename精准推断返回的组件类型:
function getRowComponentByType<K extends EntityTypeName>(typename: K): RowComponent<K> { return rowComponentMap[typename]; }
3. 重构通用Rows组件
利用泛型和类型映射,确保列表类型和行组件类型严格匹配:
interface RowsProps<K extends EntityTypeName> { list: EntityTypeMap[K][]; } const Rows = <K extends EntityTypeName>({ list }: RowsProps<K>) => { if (list.length === 0) { // 空列表提示,注意添加colSpan避免表格布局错乱 return <tr><td colSpan="100%">暂无数据</td></tr>; } // 从第一个元素的__typename推断当前列表的实体类型 const typename = list[0].__typename as K; const Row = getRowComponentByType(typename); return ( <> {list.map((item) => ( <Row key={item.id} data={item} /> ))} </> ); }; export default Rows;
4. 使用示例
在业务组件中直接传入对应类型的列表即可,TypeScript会自动校验类型:
const CatList = () => { const { list, total } = useQuery<Cat[]>(/* 你的数据查询逻辑 */); return ( <BaseTable> <Rows list={list} /> </BaseTable> ); }; const DogList = () => { const { list, total } = useQuery<Dog[]>(/* 你的数据查询逻辑 */); return ( <BaseTable> <Rows list={list} /> </BaseTable> ); };
关键优化点
- 类型映射表:通过
EntityTypeMap让TypeScript明确__typename和实体类型的绑定关系,避免联合类型的模糊推断 - 精准组件推断:
getRowComponentByType利用泛型约束,返回与输入__typename严格对应的组件类型 - 自动类型校验:业务组件传入列表时,TypeScript会自动校验列表类型是否在
EntityTypeMap中,提前发现类型错误
内容的提问来源于stack exchange,提问作者Anthony Min
相关产品推荐
相关产品推荐

