如何更优实现TypeScript显式类型「或」逻辑?替代联合+never方案
TypeScript 严格互斥联合类型的动态实现
问题背景
在TypeScript中,默认联合类型允许同时包含各分支类型的所有属性,无法实现“严格二选一”的约束。例如以下代码:
export interface BaseMenuItem { title: string; icon: string; } export interface ComponentMenuItem extends BaseMenuItem { component: any; } export interface ClickMenuItem extends BaseMenuItem { click: any; } export type MenuItem = ComponentMenuItem | ClickMenuItem; // 不符合预期:同时包含click和component属性却不触发编译错误 const myMenuItem: MenuItem = { title: 'blah', icon: 'blah2', click: true, component: true, }
手动给每个接口添加never类型的可选属性可以解决问题,但当联合类型分支或独有属性增多时,维护成本高且代码冗余:
export interface ComponentMenuItem extends BaseMenuItem { component: any; click?: never; // 手动添加互斥约束 } export interface ClickMenuItem extends BaseMenuItem { click: any; component?: never; // 手动添加互斥约束 }
动态解决方案
可以通过自定义工具类型自动生成严格互斥的联合类型,无需手动维护never属性:
// 定义工具类型:自动为联合类型分支添加其他分支属性的never约束 type ExclusifyUnion<T, U = T> = T extends any ? T & Partial<Record<Exclude<keyof U, keyof T>, never>> : never; // 原始接口定义保持不变 export interface BaseMenuItem { title: string; icon: string; } export interface ComponentMenuItem extends BaseMenuItem { component: any; } export interface ClickMenuItem extends BaseMenuItem { click: any; } // 生成严格互斥的联合类型 export type MenuItem = ExclusifyUnion<ComponentMenuItem | ClickMenuItem>; // 触发编译错误:同时包含click和component属性,不符合约束 const invalidMenuItem: MenuItem = { title: 'blah', icon: 'blah2', click: true, component: true, }; // 合法实例:仅包含对应分支的独有属性 const validComponentItem: MenuItem = { title: '组件菜单', icon: 'icon-component', component: () => {} }; const validClickItem: MenuItem = { title: '点击菜单', icon: 'icon-click', click: () => {} };
原理说明
ExclusifyUnion利用TypeScript的分布式条件类型特性:
- 遍历联合类型的每个分支
T - 将
T与一个部分类型交叉,该部分类型会把其他分支(从U中排除T的键)的属性设为never - 最终生成的联合类型每个分支都会自动带上对其他分支属性的互斥约束
这种方式的优势在于:新增类型分支或修改独有属性时,只需更新原始接口定义,工具类型会自动处理所有互斥约束,代码简洁且易维护。
内容的提问来源于stack exchange,提问作者IfTrue
相关产品推荐
相关产品推荐

