You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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'

错误原因

你的实现有两个核心问题:

  1. 每个分支里把目标属性设为true,虽然JSX中isEasy等价于isEasy={true}类型匹配,但TS会尝试匹配所有联合分支,当传isEasy时,会触发MyInterfaceMedium分支的isEasy: never检查,导致类型冲突。
  2. 用?: 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 11:42:28