reactjs - Hur skriver man en generisk typ som inkluderar alla props från en generisk typ som inte finns i en annan?
Jag försöker skriva en generell HOC som tar en komponent med props TProps och en typ TEntry som definierar en objektstruktur. Jag vill att HOC ska returnera en komponent som kommer att anropa en hook för att få en objektinstans av typen TEntry från en viss källa (i det här fallet ett CMS). Den resulterande komponenten bör endast kräva att konsumenten tillhandahåller de props som inte fylls i av dessa CMS-data.
Jag trodde att Exclude<TProps, TEntry> skulle fungera för detta, men jag hamnar fortfarande med en komponent som kräver alla TProps. Här är vad jag har försökt (detta är ganska nedskalat för enkelhetens skull):
const withCmsData = <
TEntry,
TProps,
>(
Component: (props: TProps) => (JSX.Element | null),
fallback: TEntry,
) => {
return (props: Exclude<TProps, TEntry>) => {
const { entry } = useCmsEntries<TEntry>({ fallback });
return <Component {...entry} {...props} />;
};
};
...och här är ett exempel på hur du skulle använda det för att skapa en ny komponent med färre props:
type ArticlePageProps = {
title: string;
subTitle: string;
date: string;
}
const ArticlePage = ({ title, subTitle, date }: ArticlePageProps) => {
return (
<>
<h1>{title}</h1>
<div>{subTitle}</div>
<div>{date}</div>
</>
);
}
type ArticleFromCmsProps = {
title: string;
subTitle: string;
}
/*
This creates a new component that now only requires a data prop,
which is the one prop from ArticlePageProps that is
not provided by ArticleFromCmsProps:
*/
const fallback: ArticleFromCmsProps = getFallback();
const MyPageWrapper = withCmsData<ArticlePageProps, ArticleFromCmsProps>(ArticlePage, fallback);
const render = () => {
const data = new Date();
<MyPageWrapper date={date} />
}
I exemplet ovan förväntar jag mig att få en komponent som endast kräver date-propen, men istället får jag under design-tid i VSCode varningar om att komponenten fortfarande kräver alla 3 props från ArticlePageProps -- <MyPageWrapper date={date} /> visas med felmeddelandet att title och subtitle saknas
Finns det ett sätt att få den generiska HOC jag vill ha? En som returnerar en komponent som bara kräver props som inte specificerats av TEntry-typparametern?
Svar #1
Jag har inkluderat några typer av hjälpverktyg för att minska upprepning, men huvudidén är att du accepterar fallback-objektet och den komponent som du vill rendera, och härleder deras typer med generics för att skapa den abstrakta komponentsignaturen som använder hjälpverktyget Omit<Type, Keys> för att skapa propsen.
På grund av problem i sättet TS hanterar bivarians för funktionsparametrar behöver
Props-typen förFunctionComponentvara ganska tillåtande för att undvika att behöva skriva över komponent- och prop-typer med hjälp av påståenden i HOF-kroppen.
TS Lekplats
import {
default as React,
type ReactElement,
} from 'react';
// You didn't show this type in your question, so I made a best-effort guess:
declare function useCmsEntries <T>(param: { fallback: T; }): { entry: T };
type FunctionComponent<Props extends object = any> = (props: Props) => ReactElement | null;
type ComponentProps<Component extends FunctionComponent> = Parameters<Component>[0];
type ComponentWithExcludedProps<
Props extends object,
Component extends FunctionComponent<Props>,
> = FunctionComponent<Omit<ComponentProps<Component>, keyof Props>>;
type ArticlePageProps = {
date: string;
subTitle: string;
title: string;
};
function createComponentWithoutFallbackProps<
IncludedProps extends object,
C extends FunctionComponent,
>(
fallback: IncludedProps,
Component: C,
): ComponentWithExcludedProps<IncludedProps, C> {
return (props) => {
const { entry } = useCmsEntries<IncludedProps>({ fallback });
// Make sure you spread these in the desired order: props in the latter will overwrite the former
const all: ComponentProps<C> = {...entry, ...props};
return <Component {...all} />;
};
}
declare const ArticlePage: (props: ArticlePageProps) => ReactElement;
type ArticleFromCmsProps = Pick<ArticlePageProps, 'subTitle' | 'title'>;
declare const fallback: ArticleFromCmsProps;
const MyPageWrapper = createComponentWithoutFallbackProps(fallback, ArticlePage);
type MyPageWrapperProps = Parameters<typeof MyPageWrapper>[0]; // { date: string; } 👍
const render = () => {
const date = new Date().toISOString();
<MyPageWrapper date={date} /> // ok
};
