TypeScript判别式联合中为何需将href声明为可选undefined?
React + TypeScript Logo组件类型校验问题解析
我用React和TypeScript开发了一个<Logo>组件,包含两种形态:
- 直接渲染SVG
- 将SVG包裹在链接中
两种形态共享部分Props,带链接的形态额外拥有专属属性,我用布尔判别式属性isLink区分这两种形态。但遇到了TypeScript校验异常:只有在非链接形态的接口里把href声明为href?: undefined,才能实现预期校验逻辑——即不传入isLink时使用href会报错,传入isLink={true}时必须传href。
问题示例
预期报错的错误用法
const Mycomponent = <Logo label="payment" variant="paypal" href="http://paypal.com" /> // 预期:缺少isLink却使用href,应该触发报错
正确用法
const Mycomponent = <Logo isLink label="payment" variant="paypal" href="http://paypal.com" /> // 正确:isLink为true,必须传入href
可正常校验的关键代码
import React, { FC } from "react"; type ValidRel = "alternate" | "author" | "bookmark"; export type LinkProps = { rel?: ValidRel; href: string; target?: "_blank" | "_self" | "_parent" | "_top"; }; interface CommonProps { variant: "paypal" | "visa" | "mastercard"; className?: string; }; interface LogoBase extends CommonProps { label?: string; }; interface Logo extends LogoBase { isLink?: false; // 必须保留此行,否则非链接形态可传入href且不报错 href?: undefined; }; interface InteractiveLogo extends LogoBase, LinkProps { isLink: true; label: string; }; export type Props = InteractiveLogo | Logo; const Logo: FC<Props> = ({ variant, label, className, ...unionProps }) => { if ("href" in unionProps) { return ( <a {...unionProps}> <p className={className} aria-label={label}>{variant} </p> </a> ); } return <p className={className} aria-label={label}>{variant} </p> };
异常情况分析
- 移除
href?: undefined后校验失效
如果删掉Logo接口里的href?: undefined,不传入isLink时也能传入href,完全不会触发预期的报错,类型校验逻辑直接失效:
interface Logo extends LogoBase { isLink?: false; // 已移除href?: undefined };
- 固定字符串联合类型可正常校验
如果把LinkProps里的href定义为固定字符串联合类型(比如"www.google.es" | "www.hotmail.com"),即使不写href?: undefined,类型校验也能正常工作,但这种写法无法支持任意URL,不符合业务需求:
export type LinkProps = { rel?: ValidRel; href: "www.google.es" | "www.hotmail.com"; // 固定联合类型时校验正常 target?: "_blank" | "_self" | "_parent" | "_top"; };
原因解析
这本质是TypeScript联合类型的可辨识性与属性兼容性规则导致的:
- 当联合类型的两个成员没有明确属性冲突时,TypeScript会允许“交叉赋值”。比如
Logo接口未定义href时,会被认为与带有href: string的InteractiveLogo兼容——因为Logo的href被隐式视为可选(等价于href?: any),所以传入href不会触发报错。 - 给
Logo加上href?: undefined后,相当于明确声明该形态下href只能是undefined或不存在,与InteractiveLogo里的href: string形成属性冲突,TypeScript就能通过这个冲突区分两个类型:传入字符串类型的href时,只能匹配InteractiveLogo,此时必须传入isLink: true;不传入href或传入href: undefined时,匹配Logo,此时isLink只能是false或不传入。 - 当
href是固定字符串联合类型时,TypeScript会认为该类型辨识度足够高,无需额外的href?: undefined就能区分联合成员,所以校验正常,但这种写法不适用于任意URL的场景。
优化方案(可选)
除了保留href?: undefined,还可以用严格可辨识联合类型写法,彻底禁止非链接形态传入href:
// 重新定义Props,用isLink作为严格判别式 type LogoProps = CommonProps & ( | { isLink?: false; label?: string; href?: never; // 直接禁止href属性存在 } | { isLink: true; label: string; href: string; rel?: ValidRel; target?: "_blank" | "_self" | "_parent" | "_top"; } );
这里用href?: never比href?: undefined更严格,能彻底避免非链接形态传入href的错误用法。
内容的提问来源于stack exchange,提问作者Barleby
相关产品推荐
相关产品推荐

