如何在TypeScript中定义含不同类型参数的泛型接口数组?
TypeScript泛型数组元素类型不固定的接口定义与实现方案
问题背景
我需要定义IGetInquiryProps接口,其中tableArray属性是IAntDataGridConvertedProps元素的数组,但数组中每个元素对应的IAntDataGridConvertedProps<T, K, G>泛型参数(T、K、G)并不固定,需要实现类型安全的泛型传递。
原始代码
export interface baseLazyParam { first?: number; rows: number; page: number; sortOrder?: number; sortField?: string; } export type extraPrams<InputQuery> = Omit< InputQuery, | 'sortingParams' | 'filterParam' | 'groupingColumns' | 'pageNumber' | 'pageSize' >; // 创建与BaseProperties键一致的映射类型 export type IFilter<T> = Record<keyof T, IFilterProperty>; export type ILazyParam<InputQuery, selectedFilters> = baseLazyParam & extraPrams<InputQuery> & { filters: IFilter<selectedFilters> }; export interface IFilterProperty { operator: string; constraints: any[]; } // 待修正的接口:泛型参数无法适配数组中元素的不同类型 export interface IGetInquiryProps <?,?,?>{ headerCardGroupProps: any; footerCardGroupProps: any; tableArray: IAntDataGridConvertedProps<?,?,?>[]; footerButtonsModalProps: any; } export interface IAntDataGridConvertedProps<T, K, G> { path?: string; ApiObj?: any; Columns?: any[]; tbData?: G[]; lazyParam?: ILazyParam<T, K>; recallApi?: boolean; // 其他属性... }
// 期望的组件使用方式 const GetInquiryIndividualSamaCheque = forwardRef((props: IProps, ref) => { const { recordData, requestData, samaChequeSourceDetailId, showFooter } = props; const { getInquiryProps } = useLogicGetInquiryIndividualSamaCheque({ ref, recordData, requestData, showFooter, samaChequeSourceDetailId, }); return ( <GetInquiry< [ GetAllSamaChequeDetailControlsQuery, GetAllSamaChequeDetailControlsQuery, ], [ selectedFiltersGetInquirySamaCheque, selectedFiltersGetInquirySamaCheque, ], [SamaChequeDetailControlViewModel, SamaChequeDetailControlViewModel] > {...getInquiryProps} </GetInquiry> ); }); // 待修正的组件定义 function GetInquiry<T, K, G>(props: IGetInquiryProps<T, K, G>) { const { headerCardGroupProps, footerCardGroupProps, tableArray, footerButtonsModalProps, } = props; return ( <> {headerCardGroupProps && ( <DataDisplayCardGroup {...headerCardGroupProps} /> )} {tableArray?.map((tbData, index) => { return <AntDataGridConvert {...tbData} key={index} />; })} {footerCardGroupProps && ( <DataDisplayCardGroup {...footerCardGroupProps} /> )} {footerButtonsModalProps && ( <FooterButtonsModal {...footerButtonsModalProps} /> )} </> ); }
解决方案
核心思路是用元组类型承载每个数组元素对应的泛型参数集合,再通过TypeScript的映射类型将元组的每个索引对应到IAntDataGridConvertedProps的具体泛型实例,实现数组元素的类型安全。
简化示例验证
先通过简化示例理解实现逻辑:
// 父组件代码 import ChildComponent from '@pages/example/ChildComponent.tsx'; export interface A1 { a1: number; } export interface A2 { a2: number; } export interface B1 { b1: number; } export interface B2 { b2: number; } export interface C1 { data: { id: number; header: string; flag: boolean; title: string; }[]; } export interface C2 { data: { id: number; header: string; flag: boolean; title: string; }[]; } function ParentComponent() { const apiA1 = { a1: 1 }; const paramsB1 = { b1: 1 }; const tbDataC1 = { data: [ { id: 101, header: '示例内容1', flag: true, title: '表格标题1', }, ], }; const apiA2 = { a2: 1 }; const paramsB2 = { b2: 1 }; const tbDataC2 = { data: [ { id: 103, header: '示例内容3', flag: false, title: '表格标题3', }, ], }; return ( <ChildComponent<[A1, A2], [B1, B2], [C1, C2]> tableArray={[ { api: apiA1, params: paramsB1, tbData: tbDataC1 }, { api: apiA2, params: paramsB2, tbData: tbDataC2 }, ]} </ChildComponent> ); } export default ParentComponent;
// 子组件代码 import AntDataGridConvert from '@/component/AntDataGrid/AntDataGridConvert.tsx'; import { IAntDataGridConvertedProps } from '@/component/AntDataGrid/types.ts'; export interface ITable<T, K, G> { api: T; params: K; tbData: G; } // 定义子组件接口:用元组T、K、G分别对应数组每个元素的三类泛型参数 export interface IChildComponent< T extends any[], K extends Record<keyof T, any>, G extends Record<keyof T, any>, > { tableArray: { [I in keyof T]: ITable<T[I], K[I], G[I]> }; } // 子组件实现 function ChildComponent< T extends any[], K extends Record<keyof T, any>, G extends Record<keyof T, any>, >({ tableArray }: IChildComponent<T, K, G>) { return ( <div> {( tableArray as { [I in keyof T]: IAntDataGridConvertedProps<T[I], K[I], G[I]>; } )?.map((props, index) => { return <AntDataGridConvert {...props} key={index} />; })} </div> ); } export default ChildComponent;
适配原问题的最终代码
将上述思路应用到原始问题中,修改IGetInquiryProps接口和GetInquiry组件:
// 修改后的IGetInquiryProps接口 export interface IGetInquiryProps< T extends any[], K extends Record<keyof T, any>, G extends Record<keyof T, any> > { headerCardGroupProps: any; footerCardGroupProps: any; // 通过映射类型让数组每个元素对应各自的泛型参数 tableArray: { [I in keyof T]: IAntDataGridConvertedProps<T[I], K[I], G[I]> }; footerButtonsModalProps: any; } // 修改后的GetInquiry组件 function GetInquiry< T extends any[], K extends Record<keyof T, any>, G extends Record<keyof T, any> >(props: IGetInquiryProps<T, K, G>) { const { headerCardGroupProps, footerCardGroupProps, tableArray, footerButtonsModalProps, } = props; return ( <> {headerCardGroupProps && ( <DataDisplayCardGroup {...headerCardGroupProps} /> )} {tableArray?.map((tbData, index) => { return <AntDataGridConvert {...tbData} key={index} />; })} {footerCardGroupProps && ( <DataDisplayCardGroup {...footerCardGroupProps} /> )} {footerButtonsModalProps && ( <FooterButtonsModal {...footerButtonsModalProps} /> )} </> ); }
此时你可以按照最初期望的方式使用组件,TypeScript会自动校验tableArray中每个元素的泛型类型是否与传入的元组参数匹配。
内容的提问来源于stack exchange,提问作者Taniya Ghorbani
相关产品推荐
相关产品推荐

