React+TypeScript多态组件:通过Props选择元素类型
如何让TextBody组件根据span属性动态继承对应元素的HTML属性
当然可以实现,我们可以通过TypeScript的区分联合类型或函数重载来达成需求,让组件在不同span取值下仅接收对应元素的合法HTML属性,避免联合类型带来的属性冗余问题。
方案1:使用区分联合类型
先定义两种分别对应span为true和false的Props接口,再通过联合类型组合,以span作为区分符:
import { HTMLAttributes } from "react"; import classNames from "classnames"; // 定义两种状态的Props类型 type TextBodyProps = | ({ span: true } & HTMLAttributes<HTMLSpanElement> & { type: "s" | "m" }) | ({ span?: false } & HTMLAttributes<HTMLParagraphElement> & { type: "s" | "m" }); export const TextBody = ({ span, type, className, children, ...props }: TextBodyProps) => { const textBodyClassNames = { s: "text-body-s font-light leading-relaxed max-w-sm", m: "text-body-m font-light leading-relaxed max-w-sm", }; const TextBodyElement = span ? "span" : "p"; return ( <TextBodyElement {...props as any} className={classNames(textBodyClassNames[type], className)} > {children} </TextBodyElement> ); };
当传入span={true}时,组件仅接收HTMLSpanElement的属性;不传或传入span={false}时,仅接收HTMLParagraphElement的属性。
方案2:使用函数重载 + 泛型
如果想避免类型断言,可以用函数重载明确不同参数下的属性校验规则:
import { HTMLAttributes } from "react"; import classNames from "classnames"; // 重载签名1:span为true时,匹配span元素的属性 function TextBody(props: { span: true, type: "s" | "m" } & HTMLAttributes<HTMLSpanElement>): JSX.Element; // 重载签名2:span为false或不传时,匹配p元素的属性 function TextBody(props: { span?: false, type: "s" | "m" } & HTMLAttributes<HTMLParagraphElement>): JSX.Element; // 实现签名 function TextBody({ span, type, className, children, ...props }: { span?: boolean, type: "s" | "m" } & (HTMLAttributes<HTMLSpanElement> | HTMLAttributes<HTMLParagraphElement>)) { const textBodyClassNames = { s: "text-body-s font-light leading-relaxed max-w-sm", m: "text-body-m font-light leading-relaxed max-w-sm", }; const TextBodyElement = span ? "span" : "p"; return ( <TextBodyElement {...props} className={classNames(textBodyClassNames[type], className)} > {children} </TextBodyElement> ); } export { TextBody };
这种方式下,TypeScript会根据你传入的span值自动校验属性合法性,无需额外类型断言。
效果验证
// 合法,p元素支持align属性 <TextBody type="m" align="center">段落文本</TextBody> // 报错,span元素无align属性 <TextBody type="s" span={true} align="center">行内文本</TextBody>
内容的提问来源于stack exchange,提问作者Ilija Ivic
相关产品推荐
相关产品推荐

