如何定义React组件Prop类型兼容单独/循环使用且不破坏智能提示
场景说明
现有一个Heading标题组件,其类型定义如下:
// Heading/index.d.ts import { HTMLAttributes } from 'react'; export const HeadingType: { product: 'product'; marketing: 'marketing'; }; export const HeadingLevel: { h1: 'h1'; h2: 'h2'; h3: 'h3'; h4: 'h4'; h5: 'h5'; h6: 'h6'; }; export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> { as: keyof typeof HeadingLevel; type?: keyof typeof HeadingType; } export const Heading: React.FC<HeadingProps>;
当前定义下as属性会被推导为"h1" | "h2" | "h3" | "h4" | "h5" | "h6"联合类型,单独使用组件时IntelliSense智能提示体验良好,符合预期。
但在使用map等方法循环动态生成组件时,若传入的层级值为宽泛的string类型(如模板字符串生成的`h${number}`格式值),会触发TS类型报错:
Type '`h${number}`' is not assignable to type '"h1" | "h2" | "h3" | "h4" | "h5" | "h6"'.ts(2322) index.d.ts(17, 3): The expected type comes from property 'as' which is declared here on type 'IntrinsicAttributes & HeadingProps & { children?: ReactNode; }'
待解决问题
如何调整类型定义,让组件同时兼容单独使用、循环动态渲染两种场景,且不破坏单独使用时的智能提示效果?
解决方案
不需要修改原有JS侧导出的HeadingLevel常量,仅调整类型声明文件中as属性的定义即可,修改后的完整类型定义如下:
// Heading/index.d.ts import { HTMLAttributes } from 'react'; export const HeadingType: { product: 'product'; marketing: 'marketing'; }; export const HeadingLevel: { h1: 'h1'; h2: 'h2'; h3: 'h3'; h4: 'h4'; h5: 'h5'; h6: 'h6'; }; // 提取合法的标题层级联合类型 type ValidHeadingLevel = keyof typeof HeadingLevel; export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> { // 优先保留字面量类型用于智能提示,追加模板字面量类型兜底兼容动态传值 as: ValidHeadingLevel | (`h${number}` & {}); type?: keyof typeof HeadingType; } export const Heading: React.FC<HeadingProps>;
该写法的效果:
- 单独使用组件时,TS会优先识别
ValidHeadingLevel字面量联合,输入as属性时依然会自动弹出h1~h6的补全提示,和原有体验完全一致 - 循环场景下传入模板字符串生成的
`h${number}`类型值时,不会再触发类型报错 - 传入明显不符合格式的非法值(如
"div"、"p"、"heading1")时,TS依然会抛出类型错误,不会放任非法输入 - 完全兼容原有JS导出的
HeadingLevel常量,不需要修改运行时代码
可选运行时优化
动态传入的数字可能超出1~6的范围,生成h0、h7这类浏览器不识别的HTML标签,建议在组件内部加一层轻量校验,兜底处理非法值:
// 组件JS逻辑内 const VALID_LEVELS = Object.keys(HeadingLevel); // 传入的as值不合法时默认降级为h6,可根据业务需求调整兜底逻辑,也可追加警告日志 const actualLevel = VALID_LEVELS.includes(as) ? as : 'h6'; // 渲染时使用actualLevel替代传入的as值
补充说明
类型定义文件中导出HeadingLevel常量而非直接声明联合类型,是因为实际组件由JS编写,本身导出了同名的HeadingLevel常量对象,上述方案完全保留了原有常量的导出逻辑,不会产生任何兼容问题。
内容的提问来源于stack exchange,提问作者Praveen Puglia
相关产品推荐
相关产品推荐

