You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React TypeScript拆分传递联合类型Props报错,展开则正常的原因?

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对联合类型的类型收缩与关联保留的行为差异:

  1. 联合类型的判别式关联
    Section/Val是带判别式type的联合类型,每个成员的type和value是强绑定的——比如type="str1"必然对应value是字符串类型的对象,type="num1"必然对应数值类型的value。TypeScript能识别这种关联,从而保证类型安全。

  2. 展开运算符的特性
    使用{...item}传递props时,TypeScript会将item作为一个整体处理,保留其作为联合类型中某一具体成员的完整类型信息,不会拆分属性破坏type和value的绑定关系,因此能通过类型检查。

  3. 手动传参的类型断裂
    当你手动拆分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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 07:27:55