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

