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

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

TypeScript error when using conditional types with React components

问题

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

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

错误如下所示:

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

解决方法

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

type TypefaceGridCardProps =
  | ({
      type: "pairing";
    } & PairingCardProps)
  | ({
      type: "sample";
    } & SampleCardProps);

const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) => {
  switch (type) {
    case "sample":
      return <SampleCard {...(props as SampleCardProps)} />;

    case "pairing":
      return <PairingCard {...(props as PairingCardProps)} />;

    default:
      throw new Error(`未知类型: ${type}`);
  }
};
英文:

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.

type PairingCardProps = {
  p: string;
};

type SampleCardProps = {
  s: string;
};

export const GRID_CARD_COMPONENTS = {
  pairing: ({ p }: PairingCardProps) =&gt; &lt;div&gt;Pairing Card&lt;/div&gt;,
  sample: ({ s }: SampleCardProps) =&gt; &lt;div&gt;Sample Card&lt;/div&gt;,
};

type TypefaceGridCardProps =
  | ({ type: &quot;sample&quot; } &amp; SampleCardProps)
  | ({ type: &quot;pairing&quot; } &amp; PairingCardProps);

const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) =&gt; {
  const Card = GRID_CARD_COMPONENTS[type as keyof typeof GRID_CARD_COMPONENTS];

  if (!Card) return null;

  return &lt;Card {...props} /&gt;;
};

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.

type TypefaceGridCardProps =
  | ({
      type: &quot;pairing&quot;;
    } &amp; PairingCardProps)
  | ({
      type: &quot;sample&quot;;
    } &amp; SampleCardProps);

const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) =&gt; {
  switch (type) {
    case &quot;sample&quot;:
      return &lt;SampleCard {...(props as SampleCardProps)} /&gt;;

    case &quot;pairing&quot;:
      return &lt;PairingCard {...(props as PairingCardProps)} /&gt;;

    default:
      throw new Error(`Unknown type: ${type}`);
  }
};

答案1

得分: 4

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

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

type PairingCardProps = {
  p: string;
};

type SampleCardProps = {
  s: string;
};

type TypeMap = {
  sample: SampleCardProps;
  pairing: PairingCardProps;
};

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

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

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

type GridCardComponents = typeof GRID_CARD_COMPONENTS;

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

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

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

const TypefaceGridCard = <T extends keyof TypeMap>(props: TypefaceGridCardProps<T>) => {
  const Card = GRID_CARD_COMPONENTS[props.type];

  if (!Card) return null;

  Card(props); // 没有错误

  return <Card {...props} />; // 意外错误
}

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

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

const TypefaceGridCard = <T extends keyof TypeMap>(props: TypefaceGridCardProps<T>) => {
  const Card: FC<TypeMap[T]> = GRID_CARD_COMPONENTS[props.type];
  const correctlyTypedProps: ComponentProps<typeof Card> = props;

  if (!Card) return null;

  return <Card {...correctlyTypedProps} />; // 没有错误
};

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

const TypefaceGridCard = <T extends keyof TypeMap>(props: TypefaceGridCardProps<T>) => {
  const Card: FC<TypeMap[T]> = GRID_CARD_COMPONENTS[props.type];

  if (!Card) return null;

  return <Card {...(props as ComponentProps<typeof Card>)} />; // 没有错误
};

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:

type PairingCardProps = {
  p: string;
};

type SampleCardProps = {
  s: string;
};

type TypeMap = {
  sample: SampleCardProps;
  pairing: PairingCardProps;
};

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

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

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

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:

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

Let's apply our types to the TypefaceGridCard:

const TypefaceGridCard = &lt;T extends keyof TypeMap&gt;(props: TypefaceGridCardProps&lt;T&gt;) =&gt; {
  const Card = GRID_CARD_COMPONENTS[props.type];

  if (!Card) return null;

  Card(props); // no error
  
  return &lt;Card {...props} /&gt;; // unexpected error
}

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:

const TypefaceGridCard = &lt;T extends keyof TypeMap&gt;(props: TypefaceGridCardProps&lt;T&gt;) =&gt; {
  const Card: FC&lt;TypeMap[T]&gt; = GRID_CARD_COMPONENTS[props.type];
  const correctlyTypedProps: ComponentProps&lt;typeof Card&gt; = props;

  if (!Card) return null;

  return &lt;Card {...correctlyTypedProps} /&gt;; // no error
};

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

const TypefaceGridCard = &lt;T extends keyof TypeMap&gt;(props: TypefaceGridCardProps&lt;T&gt;) =&gt; {
  const Card: FC&lt;TypeMap[T]&gt; = GRID_CARD_COMPONENTS[props.type];

  if (!Card) return null;

  return &lt;Card {...(props as ComponentProps&lt;typeof Card&gt;)} /&gt;; // no error
};

Link to StackBlitz

答案2

得分: -1

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

import React from "react";

type PairingCardProps = {
  p: string;
};

type SampleCardProps = {
  s: string;
};

export const GRID_CARD_COMPONENTS = {
  pairing: ({ p }: PairingCardProps) => <div>Pairing Card</div>,
  sample: ({ s }: SampleCardProps) => <div>Sample Card</div>,
};

type TypefaceGridCardProps =
  | { type: "sample" } & SampleCardProps
  | { type: "pairing" } & PairingCardProps;

const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) => {
  const Card = GRID_CARD_COMPONENTS[type as keyof typeof GRID_CARD_COMPONENTS];

  if (!Card) return null;

  return <Card {...props as any} />;
};

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 :

import React from &quot;react&quot;;

type PairingCardProps = {
  p: string;
};

type SampleCardProps = {
  s: string;
};

export const GRID_CARD_COMPONENTS = {
  pairing: ({ p }: PairingCardProps) =&gt; &lt;div&gt;Pairing Card&lt;/div&gt;,
  sample: ({ s }: SampleCardProps) =&gt; &lt;div&gt;Sample Card&lt;/div&gt;,
};

type TypefaceGridCardProps =
  | ({ type: &quot;sample&quot; } &amp; SampleCardProps)
  | ({ type: &quot;pairing&quot; } &amp; PairingCardProps);

const TypefaceGridCard = ({ type, ...props }: TypefaceGridCardProps) =&gt; {
  const Card = GRID_CARD_COMPONENTS[type as keyof typeof GRID_CARD_COMPONENTS];

  if (!Card) return null;

  return &lt;Card {...props as any} /&gt;;
};

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:

确定