На данный момент у меня есть два компонента:
export interface MediaCardProps {
alt: string;
src: string;
}
export const MediaCard = ({ alt, src }: MediaCardProps) => {
...
};
и
export interface TextCardProps {
isLoaded: boolean;
children: ReactNode;
}
export const TextCard = ({ children, isLoaded = false }: TextCardProps) => {
...
};
Я хотел бы создать родительский компонент следующим образом:
type Props = ({ isMedia: false } & TextCardProps) | ({ isMedia: true } & MediaCardProps);
export const Card = ({ isMedia = false, ...args }: Props) => {
if (isMedia) return <MediaCard {...(args as MediaCardProps)} />;
return <TextCard {...(args as TextCardProps)} />;
};
Цель здесь состоит в том, чтобы, когда для isMedia установлено значение true, мы видели только MediaCardProps, в противном случае мы видели TextCardProps.
Но при этом у меня возникла проблема при вызове. Я называю это так:
<Card isLoaded = {!!data.title}>
<h1 className='text-2xl font-bold'>{data.title}</h1>
</Card>
и у меня такая ошибка Type '{ children: Element; isLoaded: boolean; }' is not assignable to type 'IntrinsicAttributes & Props'. Property 'isMedia' is missing in type '{ children: Element; isLoaded: boolean; }' but required in type '{ isMedia: false; }'.
Мне удалось это исправить, изменив родительский реквизит на это:
type Props = { isMedia?: false; args: TextCardProps } | { isMedia: true; args: MediaCardProps };
но мне не нужна опора args.
Большое спасибо, если вы можете помочь мне это исправить!





Компонент вашей карты должен получить информацию о запрошенном типе носителя. Этого можно достичь, определив компонент Card как универсальный компонент, который принимает CardType в качестве универсального параметра.
Та же логика применима и к логическому типу, но для большей гибкости я заменил логический тип объединением типов:
type CardType = 'media' | 'text'
Теперь сделайте компонент Card универсальным:
const Card = <TCardType extends CardType>(props: Props<TCardType>) => { ... }
Мы передаем TCardType как общий тип в Props. Чтобы вывести его фактическое значение, нам нужно определить условную логику внутри нашего динамического универсального типа Props.
type Props<CardType> = { type?: CardType } & (CardType extends 'media' ? MediaCardProps : TextCardProps)
{ type?: CardType } позволяет сделать тип необязательным (по умолчанию 'text') и указывает тип, определяемый пользователем. Будет выведен тип этого поля, что позволит Props обеспечить правильную структуру для фактической Card реализации (с isMedia логическим значением вместо этого вы бы проверили T extends true).
Наконец, внутри общего компонента Card нам нужно только проверить, с каким реквизитом мы имеем дело. На основе этой информации мы можем вернуть соответствующую реализацию Card:
const Card = <TCardType extends CardType>(props: Props<TCardType>) => {
if (isMediaProps(props)) {
return <MediaCard {...props} />;
}
if (isTextProps(props)) {
return <TextCard {...props} />;
}
return null
};
Вот вам площадка TS с готовым решением.
Возможно, вам придется добавить CardType по умолчанию в универсальный компонент Card, чтобы всегда использовать text. const Card = <TCardType extends CardType = 'text'>(props: Props<TCardType>) => {. Я обновил ссылку TS Playground новым кодом.
Похоже, эта тема SO объясняет это хорошо stackoverflow.com/a/75562574/8997321
Прежде всего, большое спасибо, все было действительно ясно. У меня все еще есть проблема, когда я вызываю такие вещи, как
<Card isLoaded = {true} alt=''>...</Card>. Я не смогу использовать параметр alt, поскольку он должен быть специфичен для типа носителя. Я делаю что-то неправильно ?