英文:
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) => <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} />;
};
The error I encounter is as follows:
Type PairingCardProps is missing the following properties from type 'SampleCardProps': s
Workaround
I work around this issue by type casting but find the object map solution above preferable.
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(`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>)} />; // 没有错误
};
英文:
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<TypeMap[K]> } = {
pairing: ({ p }) => <div>Pairing Card</div>,
sample: ({ s }) => <div>Sample Card</div>,
};
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 'react';
// ...
type TypefaceGridCardProps<T extends keyof TypeMap = keyof TypeMap> = {
[K in T]: { type: K } & ComponentProps<GridCardComponents[K]>;
}[T];
Let's apply our types to the TypefaceGridCard
:
const TypefaceGridCard = <T extends keyof TypeMap>(props: TypefaceGridCardProps<T>) => {
const Card = GRID_CARD_COMPONENTS[props.type];
if (!Card) return null;
Card(props); // no error
return <Card {...props} />; // 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<TypeMap[K]>
and define another variable for props that is typed as ComponentProps<typeof Card>
and has a value of 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} />; // 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 = <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>)} />; // no error
};
答案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 "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;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论