打造类型安全的React多态组件:TS类型错误排查求助
React多态组件类型安全问题排查
我们尝试为一批React设计系统的多态组件提升类型安全性(例如仅允许<label>标签使用htmlFor属性),以下是简化示例:基于一个基础多态组件进行扩展,设置不同的默认as属性。
import React, { ComponentPropsWithoutRef, ReactNode, } from "react"; import { jsx as createComponent } from "@emotion/react"; type AsDomTags = "span" | "div" | 'label'; type StandardComponentWithProps< As extends AsDomTags = "span", ExtraProps extends {} = {} > = ComponentPropsWithoutRef<As> & { as?: As } & ExtraProps; type BodyProps<As extends AsDomTags = "span"> = StandardComponentWithProps< As, { children: ReactNode; } >; const Body = <As extends AsDomTags = "span">({ as, children, ...domAttributes }: BodyProps<As>) => createComponent( as || "span", { ...domAttributes, }, children ); // WRAPPED COMPONENT (extends from <Body /> component): --------------------------------------------------- type TestWrappedComponentProps<As extends AsDomTags = "div"> = BodyProps<As> & { somethingElse?: boolean }; function TestWrappedComponent<As extends AsDomTags = "div">( { as, somethingElse, ...props }: TestWrappedComponentProps<As> ) { return <Body as={as || 'div'} {...props} />; } function App() { return ( <TestWrappedComponent as="label" htmlFor="moo" somethingElse>moo</TestWrappedComponent> ); }
运行时触发TSC错误:
Type '{ as: "div" | As; } & Omit<TestWrappedComponentProps<As>, "as" | "somethingElse">' is not assignable to type 'IntrinsicAttributes & BodyProps<"div" | As>'. Type '{ as: "div" | As; } & Omit<TestWrappedComponentProps<As>, "as" | "somethingElse">' is not assignable to type 'IntrinsicAttributes & PropsWithoutRef<ComponentProps<As>> & { as?: "div" | As | undefined; } & { children: ReactNode; }'. Type '{ as: "div" | As; } & Omit<TestWrappedComponentProps<As>, "as" | "somethingElse">' is not assignable to type 'PropsWithoutRef<ComponentProps<As>>'.
问题原因分析
类型错误的核心在于:当你在TestWrappedComponent内部将as设置为as || 'div'时,TypeScript无法正确推断联合类型与BodyProps的兼容性:
TestWrappedComponentProps<As>中的As是泛型参数,当外部传入as="label"时,As被推断为"label",但组件内部的逻辑让as的类型变成了As | "div"- 此时传递给
Body的as是联合类型,但BodyProps<As>期望的as是单一的As类型,两者不匹配,进而引发DOM属性的类型校验失败
解决方案
方案1:显式指定Body的泛型类型
修改TestWrappedComponent的返回语句,明确告知TypeScriptBody的泛型是As | "div":
function TestWrappedComponent<As extends AsDomTags = "div">( { as, somethingElse, ...props }: TestWrappedComponentProps<As> ) { return <Body<As | "div"> as={as || 'div'} {...props} />; }
方案2:优化泛型类型逻辑
调整组件内部的参数处理,把默认as的逻辑交给类型定义,而非运行时判断:
type TestWrappedComponentProps<As extends AsDomTags = "div"> = BodyProps<As> & { somethingElse?: boolean }; function TestWrappedComponent<As extends AsDomTags = "div">( props: TestWrappedComponentProps<As> ) { const { somethingElse, ...bodyProps } = props; return <Body {...bodyProps} />; } // 使用时若不传as,会自动使用类型定义中的默认值"div"
内容的提问来源于stack exchange,提问作者glo
相关产品推荐
相关产品推荐

