如何实现TypeScript接口最多仅包含指定三个互斥属性之一
实现最多包含一个难度属性的React组件TypeScript接口
问题背景
现有React组件的TypeScript接口如下:
export interface MyInterface { name: string; isEasy?: boolean; isMedium?: boolean; isHard?: boolean; }
要求该接口最多只能包含isEasy、isMedium、isHard三个属性中的一个,示例如下:
- 合法:
<MyComponent name='John' /> - 合法:
<MyComponent name='John' isEasy /> - 非法:
<MyComponent name='John' isEasy isHard />
尝试用联合类型实现但未成功,代码如下:
interface MyInterface { name: string; } interface MyInterfaceEasy extends MyInterface { isEasy: true; isMedium?: never; isHard?: never; } interface MyInterfaceMedium extends MyInterface { isEasy: never; isMedium?: true; isHard?: never; } interface MyInterfaceHard extends MyInterface { isEasy: never; isMedium?: never; isHard?: true; } export type ExportedInterface = | MyInterfaceEasy | MyInterfaceMedium | MyInterfaceHard;
测试<MyComponent name='John' isEasy />时出现错误:
Types of property 'isEasy' are incompatible. Type 'boolean' is not assignable to type 'undefined'
错误原因
你的实现有两个核心问题:
- 每个分支里把目标属性设为
true,虽然JSX中isEasy等价于isEasy={true}类型匹配,但TS会尝试匹配所有联合分支,当传isEasy时,会触发MyInterfaceMedium分支的isEasy: never检查,导致类型冲突。 - 用
?: never定义非目标属性无效,因为可选属性如果没传,TS不会强制它不存在,无法阻止同时传入多个难度属性。
正确实现方案
方案一:明确分支约束
直接定义每个合法情况的类型,确保每个分支里只有目标难度属性可选,其他两个属性必须不存在:
// 基础属性 interface BaseProps { name: string; } // 无任何难度属性的情况 type NoDifficultyProps = BaseProps & { isEasy?: never; isMedium?: never; isHard?: never; }; // 仅包含isEasy的情况 type EasyProps = BaseProps & { isEasy?: boolean; isMedium?: never; isHard?: never; }; // 仅包含isMedium的情况 type MediumProps = BaseProps & { isEasy?: never; isMedium?: boolean; isHard?: never; }; // 仅包含isHard的情况 type HardProps = BaseProps & { isEasy?: never; isMedium?: never; isHard?: boolean; }; // 组合所有合法类型 export type MyComponentProps = NoDifficultyProps | EasyProps | MediumProps | HardProps;
方案二:用工具类型简化代码
利用TS的Exclude和Pick工具类型,批量生成单个难度属性的类型,减少重复代码:
// 基础属性 interface BaseProps { name: string; } // 定义所有难度属性的键名 type DifficultyKeys = 'isEasy' | 'isMedium' | 'isHard'; // 生成仅包含指定单个难度属性的类型 type SingleDifficultyProps<K extends DifficultyKeys> = BaseProps & // 允许目标属性可选(boolean类型) Partial<Pick<Record<DifficultyKeys, boolean>, K>> & // 强制其他难度属性不存在(类型为never) Record<Exclude<DifficultyKeys, K>, never>; // 组合所有合法情况:无难度 + 单个难度 export type MyComponentProps = BaseProps | SingleDifficultyProps<'isEasy'> | SingleDifficultyProps<'isMedium'> | SingleDifficultyProps<'isHard'>;
验证效果
<MyComponent name='John' />→ 合法(匹配BaseProps或NoDifficultyProps)<MyComponent name='John' isEasy />→ 合法(匹配EasyProps或SingleDifficultyProps<'isEasy'>)<MyComponent name='John' isEasy isHard />→ 非法(同时存在两个难度属性,不符合任何分支的约束)
内容的提问来源于stack exchange,提问作者Leo Messi
相关产品推荐
相关产品推荐

