TypeScript可选泛型实现返回对象类型推断并保留自动补全
TypeScript类型安全事件映射实现问题
需求概述
希望构建类型安全的事件系统,创建事件映射对象时强制事件符合指定分组规则,同时保留对象结构的智能提示:
type DashboardGroups = "home" | "settings" | "profile"; type DashboardEvent = IMetricsEvent<DashboardGroups>; // 事件必须符合`{ [event:string]: DashboardEvent }`结构 const DashboardEventsMap = createEventMapping<DashboardEvent>({ validEvent: { name: "valid event", group: "home" // ✅ 校验通过 }, invalidEvent: { name: "invalid event", group: "invalid group", // ❌ 触发类型错误 } }) // 使用时保留智能提示 DashboardEventsMap.validEvent // ✅ 自动补全正常 DashboardEventsMap.eventWhichDoesntExist // ❌ 触发类型错误
事件结构定义
事件接口支持自定义分组类型:
export interface IMetricsEvent< TGroup extends string = string, > { name: string; group?: TGroup; }
当前实现的问题
现有createMetricsEventMapping函数无法同时满足类型校验和智能提示需求:
type MetricsEventMapType<TEvent extends IMetricsEvent> = { [event: string]: TEvent; }; export const createMetricsEventMapping = < TMetricsEvent extends IMetricsEvent, T extends MetricsEventMapType<TMetricsEvent> = MetricsEventMapType<TMetricsEvent>, >( arg: T, ) => arg;
问题1:未传入类型参数时
自动补全正常,但分组无类型校验:
const map = createMetricsEventMapping({event: {name:"event", group:"any group"}}); map.event // ✅ 自动补全正常,但分组可任意输入
问题2:传入事件类型参数时
分组类型校验生效,但失去对象结构的智能提示:
type DashboardEvent = IMetricsEvent<"home">; const map = createMetricsEventMapping<DashboardEvent>({event: {name:"event", group:"any group"}}); map.event // ❌ 无自动补全,仅能通过字符串索引访问
问题3:移除默认类型参数
移除T的默认值后,传入单个类型参数会报错:
export const createMetricsEventMapping = < TMetricsEvent extends IMetricsEvent, T extends MetricsEventMapType<TMetricsEvent>, >( arg: T, ) => arg; type DashboardEvent = IMetricsEvent<"home">; // ❌ 错误:期望2个类型参数,但只传入了1个 const map = createMetricsEventMapping<DashboardEvent>({event: {name:"event", group:"any group"}});
解决方案
使用嵌套泛型函数实现,先指定事件类型,再传入映射对象,同时满足类型校验和智能提示需求:
export const createMetricsEventMapping = <TEvent extends IMetricsEvent>() => { // 内层函数自动推断映射对象的键值结构,同时约束值为指定的TEvent类型 return <T extends Record<string, TEvent>>(events: T): T => events; };
使用示例
type DashboardGroups = "home" | "settings" | "profile"; type DashboardEvent = IMetricsEvent<DashboardGroups>; // 先调用外层函数指定事件类型,再传入事件映射 const DashboardEventsMap = createMetricsEventMapping<DashboardEvent>()({ validEvent: { name: "valid event", group: "home" // ✅ 校验通过 }, invalidEvent: { name: "invalid event", group: "invalid group" // ❌ 触发类型错误:类型"invalid group"不能赋值给DashboardGroups } }); // 使用时保留完整的智能提示 DashboardEventsMap.validEvent; // ✅ 自动补全正常 DashboardEventsMap.eventWhichDoesntExist; // ❌ 触发类型错误:属性不存在
无类型参数场景
如果不需要约束分组类型,直接调用内层函数即可,自动推断所有类型:
const map = createMetricsEventMapping()({ event: {name:"event", group:"any group"} }); map.event; // ✅ 自动补全正常
内容的提问来源于stack exchange,提问作者Jeffrey
相关产品推荐
相关产品推荐

