reactjs 在React组件中使用条件类型时TypeScript错误

nuypyhwy  于 2023-06-22  发布在  React
关注(0)|答案(2)|浏览(172)

bounty还有3天到期。此问题的答案有资格获得+50声望奖励。msalla希望引起更多关注这个问题。

我在使用React组件的条件类型时遇到了TypeScript错误。
当尝试基于类型prop呈现不同类型的组件并为每种类型提供相应的prop时,就会出现问题。

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

我遇到的错误如下:
Type PairingCardProps is missing the following properties from type 'SampleCardProps': s

解决方案

我通过类型转换来解决这个问题,但发现上面的对象Map解决方案更好。

  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(`Unknown type: ${type}`);
  16. }
  17. };
ds97pgxw

ds97pgxw1#

编译器无法处理ms/TS#30581中描述的“相关联合类型”,但是,ms/TS#47109中描述了一个建议的重构,它考虑转移到泛型。首先,我们需要一些Map类型来保存每个组件的props:

  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参数创建一个Map类型,它将接受一个选项泛型参数K,该参数扩展了TypeMap的键,默认为keyof TypeMap。此类型将返回给定K的props或所有可能props的并集:

  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); // no error
  5. return <Card {...props} />; // unexpected error
  6. }

奇怪的是,当我们尝试以JSX格式调用组件时,我们会得到一个错误。我不确定确切的原因,但我认为它必须对React的内部泛型类型做一些事情,这些类型无法正确处理此类情况。
解决方法是手动键入CardFC<TypeMap[K]>,并为props定义另一个类型为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} />; // no error
  6. };

或者,你可以使用asAssert来代替创建另一个变量,但我不推荐这样做,因为它的类型安全性较差:

  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>)} />; // no error
  5. };

Link to StackBlitz

展开查看全部
i1icjdpr

i1icjdpr2#

我认为这是因为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;
展开查看全部

相关问题