React TypeScript拆分传递联合类型Props报错,展开则正常的原因?
我在React TypeScript代码中遇到了类型错误:只有使用展开运算符传递Props时不会报错,手动拆分属性传递就会报错,请问这是为什么?
简化复现代码
interface SString { type: "str1" | "str2"; value: { valueLiteral: string }; } interface SNumeric { type: "num1" | "num2"; value: { valueLiteral: number }; } type Section = SString | SNumeric; interface State { sections: Section[]; } const state: State = { sections: [ { type: "str1", value: { valueLiteral: "val1" } }, { type: "num1", value: { valueLiteral: 1 } } ] }; function Comp({ type, value }: Section) { return ( <div style={{marginBottom: 20}}> <div>{type}</div> <div>{value.valueLiteral}</div> </div> ); } export default function App() { return ( <div className="App"> {state.sections.map((item) => { // return <Comp key={item.type} {...item} />; // 这段代码无错误 return <Comp key={item.type} type={item.type} value={item.value} />; // 这段代码报错 })} </div> ); }
报错信息
function Comp({ type, value }: Section): JSX.Element Type '{ key: "str1" | "str2" | "num1" | "num2"; type: "str1" | "str2" | "num1" | "num2"; value: { valueLiteral: string; } | { valueLiteral: number; }; }' is not assignable to type 'IntrinsicAttributes & Section'. Type '{ key: "str1" | "str2" | "num1" | "num2"; type: "str1" | "str2" | "num1" | "num2"; value: { valueLiteral: string; } | { valueLiteral: number; }; }' is not assignable to type 'SNumeric'. Types of property 'type' are incompatible. Type '"str1" | "str2" | "num1" | "num2"' is not assignable to type '"num1" | "num2"'. Type '"str1"' is not assignable to type '"num1" | "num2"'.ts(2322)
实际需求补充
上述代码经过简化,我实际需要处理一组ValCord、ValString、ValNumeric类型的对象数组,传递给React组件后,根据对象类型调用不同的子组件:
interface ValCord { type: "padding" | "margin"; value: { x: number; y: number }; } interface ValString { type: "color" | "font"; value: string; } interface ValNumeric { type: "font-size" | "font-weight"; value: number; } type Val = ValCord | ValString | ValNumeric; interface State { sections: Val[]; } const val1: ValCord = { type: "padding", value: { y: 8, x: 10 } }; const val2: ValString = { type: "color", value: "white" }; const val3: ValNumeric = { type: "font-size", value: 12 }; const state: State = { sections: [val1, val2, val3] }; function ValCordComp({ type, value }: ValCord) { return ( <div> {type} - X:{value.x} Y:{value.y} </div> ); } function ValStringComp({ type, value }: ValString) { return ( <div> {type} is {value.toUpperCase()} </div> ); } function ValNumericComp({ type, value }: ValNumeric) { return ( <div> {type} is {value * 100} </div> ); } function Comp({ type, value }: Val) { return ( <div style={{ marginBottom: 20 }}> {type === "padding" ? <ValCordComp type={type} value={value} /> : null} {type === "color" ? <ValStringComp type={type} value={value} /> : null} {type === "font-size" ? ( <ValNumericComp type={type} value={value} /> ) : null} </div> ); } export default function App() { return ( <div className="App"> {state.sections.map((item) => { // return <Comp key={item.type} {...item} />; // 无错误 return <Comp key={item.type} type={item.type} value={item.value} />; // 报错 })} </div> ); }
问题原因
核心在于TypeScript对联合类型的类型收缩与关联保留的行为差异:
联合类型的判别式关联
Section/Val是带判别式type的联合类型,每个成员的type和value是强绑定的——比如type="str1"必然对应value是字符串类型的对象,type="num1"必然对应数值类型的value。TypeScript能识别这种关联,从而保证类型安全。展开运算符的特性
使用{...item}传递props时,TypeScript会将item作为一个整体处理,保留其作为联合类型中某一具体成员的完整类型信息,不会拆分属性破坏type和value的绑定关系,因此能通过类型检查。手动传参的类型断裂
当你手动拆分type={item.type}和value={item.value}时,TypeScript会分别推断这两个属性的类型:type被推断为所有联合成员type值的集合(比如"str1"|"str2"|"num1"|"num2")value被推断为所有联合成员value类型的集合(比如{valueLiteral:string}|{valueLiteral:number})
此时type和value的绑定关系被切断,TypeScript无法确认当前传递的type和value是否匹配联合类型中的某一个合法成员,因此抛出类型不兼容的错误。
解决方案
针对你的实际需求,推荐以下几种方案:
方案1:继续使用展开运算符
这是最简单且安全的方式,直接保留联合类型的完整性,无需额外处理:
{state.sections.map((item) => { return <Comp key={item.type} {...item} />; })}
方案2:通过类型守卫收缩类型
如果必须手动传递属性,可以在循环中添加类型守卫,明确item的具体类型,恢复type和value的关联:
{state.sections.map((item) => { if (item.type === "padding" || item.type === "margin") { return <Comp key={item.type} type={item.type} value={item.value} />; } else if (item.type === "color" || item.type === "font") { return <Comp key={item.type} type={item.type} value={item.value} />; } else { return <Comp key={item.type} type={item.type} value={item.value} />; } })}
不过这种方式需要逐一判断类型,代码冗余,不如展开运算符高效。
方案3:优化组件Props类型(不推荐)
可以给Comp组件定义一个更宽松的Props类型,或者使用类型断言绕过检查,但这会失去TypeScript的类型安全保障,仅在特殊场景下使用:
// 类型断言示例 return <Comp key={item.type} {...(item as Val)} />;
内容的提问来源于stack exchange,提问作者Evanss

