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

React组件Props的TypeScript互斥且必选类型定义问题

解决方案:用通用互斥联合类型实现二选一Props校验

核心思路

通过自定义通用互斥联合类型工具,将需要互斥的属性组生成严格的二选一约束,同时结合公共属性,既解决冗余问题,又确保必须传入其中一个属性、不能同时传入。

具体实现代码

import React from 'react';

// 通用互斥联合类型工具:生成两个类型的互斥联合,确保不能同时包含双方的独有属性
type ExclusiveUnion<T, U> = 
  (T & Record<Exclude<keyof U, keyof T>, never>) 
  | (U & Record<Exclude<keyof T, keyof U>, never>);

// 按钮公共属性(比如点击事件、禁用状态等)
type BaseButtonProps = {
  onClick?: () => void;
  disabled?: boolean;
  className?: string;
};

// 仅含图标的按钮属性
type IconButtonProps = {
  icon: React.ReactNode;
};

// 仅含文本的按钮属性
type TextButtonProps = {
  text: string;
};

// 最终按钮Props类型:公共属性 + 互斥的图标/文本属性
type ButtonProps = BaseButtonProps & ExclusiveUnion<IconButtonProps, TextButtonProps>;

// 按钮组件实现
const Button = (props: ButtonProps) => {
  const { icon, text, onClick, disabled, className } = props;
  return (
    <button onClick={onClick} disabled={disabled} className={className}>
      {icon ?? text}
    </button>
  );
};

效果验证

合法用法(无报错)

// 仅传图标
<Button icon={<svg>...</svg>} onClick={() => console.log('clicked')} />

// 仅传文本
<Button text="提交" disabled className="primary-btn" />

非法用法(TypeScript会抛出错误)

// 同时传图标和文本 → 报错:text属性类型为never,无法赋值
<Button icon={<svg>...</svg>} text="提交" />

// 图标和文本都不传 → 报错:缺少必填的icon或text属性
<Button onClick={() => {}} />

优势说明

  1. 复用性强:ExclusiveUnion是通用工具类型,后续新增其他互斥属性(比如loading状态与图标/文本互斥)时,只需定义新的属性类型,嵌套使用工具即可,无需修改原有接口:
    type LoadingButtonProps = { loading: boolean };
    type ButtonProps = BaseButtonProps & ExclusiveUnion<ExclusiveUnion<IconButtonProps, TextButtonProps>, LoadingButtonProps>;
    
  2. 约束严格:相比之前的可选never写法,这里通过联合类型强制要求必须满足其中一个分支,同时通过never属性彻底禁止同时传入互斥属性,解决了“可选导致的必填校验缺失”问题。
  3. 代码简洁:无需为每个互斥分支重复写冗余的never属性,只需定义基础属性类型即可。

内容的提问来源于stack exchange,提问作者helion3

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 13:45:23