如何正确声明包含动态泛型T值的Map/Record映射
我正尝试为固定字符串枚举类型CellTemplate中的每个键,映射对应泛型参数不同的CustomCellRenderer——这类渲染器的value参数和additionalProps支持泛型定义。相关类型与实现代码如下:
// :: 基础类型定义 :: export type CellTemplate = "checkbox" | "badge" | "label" | "link"; export interface CustomCellRenderer< TValue = unknown, TRendererProps = unknown > { ( value: TValue, key: string, label: string, row: unknown, additionalProps?: TRendererProps ): JSX.Element; } // :: 基础类型定义 :: // :: 渲染器类型实现 :: type LinkCellRendererProps = { linkTo?: string; }; export type LinkCustomCellRendererType = CustomCellRenderer< string, LinkCellRendererProps >; export type LabelCustomCellRendererType = CustomCellRenderer<string>; export type CheckboxCustomCellRendererType = CustomCellRenderer<boolean>; export type BadgeCustomCellRendererType = CustomCellRenderer<string | string[]>; // 每个渲染器遵循相同结构,示例: export const BadgeCellRenderer: BadgeCustomCellRendererType = function ( value: string | string[], key: string, label: string, row: unknown ): JSX.Element { // ... 具体渲染逻辑 }; // :: 渲染器类型实现 ::
我希望开发者指定CellTemplate类型的字符串时,能自动关联到对应的CustomCellRenderer类型。目前尝试用makeMap函数创建映射:
function makeMap<K>(map: Record<CellTemplate, K>): Record<CellTemplate, K> { return map; } // 问题:无法支持不同泛型类型的渲染器映射 const CustomComponentRenderer = makeMap({ checkbox: CheckBoxCellRenderer, badge: BadgeCellRenderer, label: LabelCellRenderer, link: LinkCellRenderer, });
但不同渲染器的泛型参数存在差异(比如LabelCellRenderer对应string类型、CheckBoxCellRenderer对应boolean类型),导致K类型冲突;使用makeMap<any>虽然能运行,但会丢失后续SampleProps接口中需要的类型提示:
interface SampleProps<TCellTemplate extends CellTemplate = CellTemplate, TRow = unknown> { template?: TCellTemplate; templateAdditionalProps?: (row: TRow) => typeof CustomComponentRenderer[TCellTemplate]; }
如何正确实现这一映射并保留完整的类型推断能力?
核心思路是让TypeScript明确每个CellTemplate键对应的具体渲染器类型,避免用强制统一类型的工具函数,直接通过类型别名或自动推断的方式建立键与类型的关联。
1. 定义模板与渲染器的类型映射
先创建一个类型别名,显式声明每个CellTemplate对应的CustomCellRenderer具体类型:
// 建立CellTemplate到对应渲染器类型的映射 type CellRendererMap = { checkbox: CheckboxCustomCellRendererType; badge: BadgeCustomCellRendererType; label: LabelCustomCellRendererType; link: LinkCustomCellRendererType; };
2. 创建类型安全的渲染器映射对象
直接用const声明映射对象,并指定类型为CellRendererMap,确保每个键对应正确的渲染器:
// 类型安全的渲染器映射 const CustomComponentRenderer: CellRendererMap = { checkbox: CheckBoxCellRenderer, badge: BadgeCellRenderer, label: LabelCellRenderer, link: LinkCellRenderer, };
3. 完善SampleProps的类型提示
为了让templateAdditionalProps能根据选中的template自动推断类型,需要一个辅助类型从CustomCellRenderer中提取TRendererProps泛型参数:
// 辅助类型:提取CustomCellRenderer的附加属性类型 type GetRendererProps<T extends CustomCellRenderer> = T extends CustomCellRenderer<any, infer P> ? P : never; interface SampleProps<TCellTemplate extends CellTemplate = CellTemplate, TRow = unknown> { template?: TCellTemplate; // 根据template自动推断对应的附加属性类型 templateAdditionalProps?: (row: TRow) => GetRendererProps<CellRendererMap[TCellTemplate]>; }
4. (可选)自动推断映射类型
如果不想手动维护CellRendererMap的类型定义,可以让TypeScript自动推断映射对象的类型:
// 创建映射并让TS自动推断类型(as const 确保类型不会被拓宽) const CustomComponentRenderer = { checkbox: CheckBoxCellRenderer, badge: BadgeCellRenderer, label: LabelCellRenderer, link: LinkCellRenderer, } as const; // 提取自动推断的映射类型 type CellRendererMap = typeof CustomComponentRenderer;
这种方式更灵活,新增模板时无需手动修改类型定义,TypeScript会自动更新CellRendererMap。
内容的提问来源于stack exchange,提问作者Francisco

