TypeScript在使用React组件时使用条件类型时出现错误。

huangapple go评论105阅读模式
英文:

TypeScript error when using conditional types with React components

问题

遇到了在使用条件类型与 React 组件时的 TypeScript 错误。

当尝试基于类型属性 (type prop) 渲染不同类型的组件并提供每种类型的相应属性时,出现了问题。

错误如下所示:

类型 PairingCardProps 缺少类型 'SampleCardProps' 的以下属性:s

解决方法

我通过类型转换来解决了这个问题,但上面的对象映射解决方案更可取。

  1. type TypefaceGridCardProps =
  2. | ({
  3. type: "pairing";
  4. } & PairingCardProps)
  5. | ({
  6. type: "sample";
  7. } & SampleCardProps);
  8. const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) => {
  9. switch (type) {
  10. case "sample":
  11. return <SampleCard {...(props as SampleCardProps)} />;
  12. case "pairing":
  13. return <PairingCard {...(props as PairingCardProps)} />;
  14. default:
  15. throw new Error(`未知类型: ${type}`);
  16. }
  17. };
英文:

I'm encountering a TypeScript error when using conditional types with React components.

The issue arises when trying to render different types of components based on a type prop and providing the corresponding props for each type.

  1. type PairingCardProps = {
  2. p: string;
  3. };
  4. type SampleCardProps = {
  5. s: string;
  6. };
  7. export const GRID_CARD_COMPONENTS = {
  8. pairing: ({ p }: PairingCardProps) =&gt; &lt;div&gt;Pairing Card&lt;/div&gt;,
  9. sample: ({ s }: SampleCardProps) =&gt; &lt;div&gt;Sample Card&lt;/div&gt;,
  10. };
  11. type TypefaceGridCardProps =
  12. | ({ type: &quot;sample&quot; } &amp; SampleCardProps)
  13. | ({ type: &quot;pairing&quot; } &amp; PairingCardProps);
  14. const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) =&gt; {
  15. const Card = GRID_CARD_COMPONENTS[type as keyof typeof GRID_CARD_COMPONENTS];
  16. if (!Card) return null;
  17. return &lt;Card {...props} /&gt;;
  18. };

The error I encounter is as follows:

Type PairingCardProps is missing the following properties from type &#39;SampleCardProps&#39;: s

Workaround

I work around this issue by type casting but find the object map solution above preferable.

  1. type TypefaceGridCardProps =
  2. | ({
  3. type: &quot;pairing&quot;;
  4. } &amp; PairingCardProps)
  5. | ({
  6. type: &quot;sample&quot;;
  7. } &amp; SampleCardProps);
  8. const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) =&gt; {
  9. switch (type) {
  10. case &quot;sample&quot;:
  11. return &lt;SampleCard {...(props as SampleCardProps)} /&gt;;
  12. case &quot;pairing&quot;:
  13. return &lt;PairingCard {...(props as PairingCardProps)} /&gt;;
  14. default:
  15. throw new Error(`Unknown type: ${type}`);
  16. }
  17. };

答案1

得分: 4

编译器无法处理在ms/TS#30581中描述的"相关联的联合类型",但是在ms/TS#47109中提出了一种建议的重构,考虑使用泛型。

首先,我们需要一些能够保存每个组件的属性的映射类型:

  1. type PairingCardProps = {
  2. p: string;
  3. };
  4. type SampleCardProps = {
  5. s: string;
  6. };
  7. type TypeMap = {
  8. sample: SampleCardProps;
  9. pairing: PairingCardProps;
  10. };

现在,让我们使用mapped type重新定义GRID_CARD_COMPONENTS,使用TypeMap作为类型源:

  1. export const GRID_CARD_COMPONENTS: { [K in keyof TypeMap]: React.FC<TypeMap[K]> } = {
  2. pairing: ({ p }) => <div>Pairing Card</div>,
  3. sample: ({ s }) => <div>Sample Card</div>,
  4. };

让我们还定义一下之后需要用到的GRID_CARD_COMPONENTS的类型:

  1. type GridCardComponents = typeof GRID_CARD_COMPONENTS;

我们还应该为TypefaceGridCard的参数创建一个映射类型,它将接受一个泛型参数K,该参数扩展了TypeMap的键,并默认为keyof TypeMap。此类型将返回给定K的属性或所有可能属性的联合:

  1. import { ComponentProps } from 'react';
  2. // ...
  3. type TypefaceGridCardProps<T extends keyof TypeMap = keyof TypeMap> = {
  4. [K in T]: { type: K } & ComponentProps<GridCardComponents[K]>;
  5. }[T];

让我们将这些类型应用于TypefaceGridCard

  1. const TypefaceGridCard = <T extends keyof TypeMap>(props: TypefaceGridCardProps<T>) => {
  2. const Card = GRID_CARD_COMPONENTS[props.type];
  3. if (!Card) return null;
  4. Card(props); // 没有错误
  5. return <Card {...props} />; // 意外错误
  6. }

奇怪的是,当我们尝试以JSX格式调用组件时会出现错误。我不确定确切的原因,但我认为与React内部的泛型类型有关,它无法正确处理这种情况。

解决方法是将Card手动类型为FC<TypeMap[K]>,并为属性定义另一个变量,该变量的类型为ComponentProps<typeof Card>,并且值为props

  1. const TypefaceGridCard = <T extends keyof TypeMap>(props: TypefaceGridCardProps<T>) => {
  2. const Card: FC<TypeMap[T]> = GRID_CARD_COMPONENTS[props.type];
  3. const correctlyTypedProps: ComponentProps<typeof Card> = props;
  4. if (!Card) return null;
  5. return <Card {...correctlyTypedProps} />; // 没有错误
  6. };

或者,您可以使用as断言而不是创建另一个变量,但我不建议这样做,因为它不太安全:

  1. const TypefaceGridCard = <T extends keyof TypeMap>(props: TypefaceGridCardProps<T>) => {
  2. const Card: FC<TypeMap[T]> = GRID_CARD_COMPONENTS[props.type];
  3. if (!Card) return null;
  4. return <Card {...(props as ComponentProps<typeof Card>)} />; // 没有错误
  5. };

StackBlitz链接

英文:

The compiler is unable to handle the "correlated union types" described in ms/TS#30581, however, there is a suggested refactor described in ms/TS#47109, which considers moving to generics.
First, we will need some map type that will hold props of every component:

  1. type PairingCardProps = {
  2. p: string;
  3. };
  4. type SampleCardProps = {
  5. s: string;
  6. };
  7. type TypeMap = {
  8. sample: SampleCardProps;
  9. pairing: PairingCardProps;
  10. };

Now, let's redefined GRID_CARD_COMPONENTS using mapped type with using TypeMap as a source for types:

  1. export const GRID_CARD_COMPONENTS: { [K in keyof TypeMap]: React.FC&lt;TypeMap[K]&gt; } = {
  2. pairing: ({ p }) =&gt; &lt;div&gt;Pairing Card&lt;/div&gt;,
  3. sample: ({ s }) =&gt; &lt;div&gt;Sample Card&lt;/div&gt;,
  4. };

Let's also define the type of the GRID_CARD_COMPONENTS which we will need afterwards:

  1. type GridCardComponents = typeof GRID_CARD_COMPONENTS;

We should also create a mapped type for arguments of TypefaceGridCard, which will accept an option generic argument K that extends the key of TypeMap and is defaulted to keyof TypeMap. This type will return the props for the given K or a union of all possible props:

  1. import {ComponentProps} from &#39;react&#39;;
  2. // ...
  3. type TypefaceGridCardProps&lt;T extends keyof TypeMap = keyof TypeMap&gt; = {
  4. [K in T]: { type: K } &amp; ComponentProps&lt;GridCardComponents[K]&gt;;
  5. }[T];

Let's apply our types to the TypefaceGridCard:

  1. const TypefaceGridCard = &lt;T extends keyof TypeMap&gt;(props: TypefaceGridCardProps&lt;T&gt;) =&gt; {
  2. const Card = GRID_CARD_COMPONENTS[props.type];
  3. if (!Card) return null;
  4. Card(props); // no error
  5. return &lt;Card {...props} /&gt;; // unexpected error
  6. }

Strangely, we get an error when we try to invoke the component in JSX format. I'm not sure about the exact reason, but I think it has to do something about React's internal generic types, which can't properly handle such cases.

The workaround is to manually type Card as FC&lt;TypeMap[K]&gt; and define another variable for props that is typed as ComponentProps&lt;typeof Card&gt; and has a value of props:

  1. const TypefaceGridCard = &lt;T extends keyof TypeMap&gt;(props: TypefaceGridCardProps&lt;T&gt;) =&gt; {
  2. const Card: FC&lt;TypeMap[T]&gt; = GRID_CARD_COMPONENTS[props.type];
  3. const correctlyTypedProps: ComponentProps&lt;typeof Card&gt; = props;
  4. if (!Card) return null;
  5. return &lt;Card {...correctlyTypedProps} /&gt;; // no error
  6. };

Alternatively, you could use as assertion instead of creating another variable, but I wouldn't recommend it, since it is less type-safe:

  1. const TypefaceGridCard = &lt;T extends keyof TypeMap&gt;(props: TypefaceGridCardProps&lt;T&gt;) =&gt; {
  2. const Card: FC&lt;TypeMap[T]&gt; = GRID_CARD_COMPONENTS[props.type];
  3. if (!Card) return null;
  4. return &lt;Card {...(props as ComponentProps&lt;typeof Card&gt;)} /&gt;; // no error
  5. };

Link to StackBlitz

答案2

得分: -1

我认为这是由于 TypeScript 导致的,因为 TypeScript 编译器无法推断TypefaceGridCard组件中的Card组件。我也遇到过这种问题。因此,可以这样解决:

  1. import React from "react";
  2. type PairingCardProps = {
  3. p: string;
  4. };
  5. type SampleCardProps = {
  6. s: string;
  7. };
  8. export const GRID_CARD_COMPONENTS = {
  9. pairing: ({ p }: PairingCardProps) => <div>Pairing Card</div>,
  10. sample: ({ s }: SampleCardProps) => <div>Sample Card</div>,
  11. };
  12. type TypefaceGridCardProps =
  13. | { type: "sample" } & SampleCardProps
  14. | { type: "pairing" } & PairingCardProps;
  15. const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) => {
  16. const Card = GRID_CARD_COMPONENTS[type as keyof typeof GRID_CARD_COMPONENTS];
  17. if (!Card) return null;
  18. return <Card {...props as any} />;
  19. };
  20. export default TypefaceGridCard;
英文:

I think this us due to typescript as typescript compiler is not able to infer the Card component in the TypefaceGridCard component. I also face this type of issue . So this can be solved as :

  1. import React from &quot;react&quot;;
  2. type PairingCardProps = {
  3. p: string;
  4. };
  5. type SampleCardProps = {
  6. s: string;
  7. };
  8. export const GRID_CARD_COMPONENTS = {
  9. pairing: ({ p }: PairingCardProps) =&gt; &lt;div&gt;Pairing Card&lt;/div&gt;,
  10. sample: ({ s }: SampleCardProps) =&gt; &lt;div&gt;Sample Card&lt;/div&gt;,
  11. };
  12. type TypefaceGridCardProps =
  13. | ({ type: &quot;sample&quot; } &amp; SampleCardProps)
  14. | ({ type: &quot;pairing&quot; } &amp; PairingCardProps);
  15. const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) =&gt; {
  16. const Card = GRID_CARD_COMPONENTS[type as keyof typeof GRID_CARD_COMPONENTS];
  17. if (!Card) return null;
  18. return &lt;Card {...props as any} /&gt;;
  19. };
  20. export default TypefaceGridCard;

huangapple
  • 本文由 发表于 2023年6月16日 01:48:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76484283.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定