reactjs - För en React + TypeScript-variabel: tvinga vissa attribut i komponentens "props"


Jag skulle vilja ha en variabel (egentligen ett funktionsargument, men jag förenklade problemet) som kan hålla en React-komponent som MÅSTE ha t.ex. aNumber: number bland sina egenskaper.

Mitt misslyckade försök var:

function MyComp(props: { aNumber: number, aString: string }) {
    return <>{props.aNumber} {props.aString}</>;
}

let f2: FunctionComponent<{ aNumber: number }> = MyComp;

ger mig felet:

Type '(props: { aNumber: number; aString: string; }) => Element' is not assignable to type 'FunctionComponent<{ aNumber: number; }>'.
  Types of parameters 'props' and 'props' are incompatible.
    Property 'aString' is missing in type 'PropsWithChildren<{ aNumber: number; }>' but required in type '{ aNumber: number; aString: string; }'.ts(2322)

FunctionComponent definieras i React som:

    interface FunctionComponent<P = {}> {
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
        propTypes?: WeakValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }

Jag förstår inte felet. Om jag definierade en variabel med en begränsning att ha aNumber, och jag tilldelade den ett rikare element => varför är den inte nöjd? Och varför klagar den på de där "rikare" attributen?

Självklart fungerar följande:

let f1: FunctionComponent<any> = MyComp;

Vanligtvis när jag försöker med avancerade typningskonstruktioner i TS, får jag det till slut att fungera efter många huvudvärk. Den här gången verkar det inte avancerat, men jag lyckades inte lista ut det.

Tack så mycket i förväg.

Svar #1

Problemet är att React-komponenter är funktioner och funktioner är kontravarianter, vilket betyder att om en funktion med parametrar av typen T krävs så kan den tillfredsställas med en funktion av en bredare typ än T men inte av en smalare typ. Det är till skillnad från, säg, vanliga objekt, som är kovarianter, dvs. de accepterar en smalare typ, men inte en bredare.

Du kan se samma sak utan någon React-kod

function MyFunc(props:{a: number, b: string}){return}

let func2: (props:{a:number})=>void = MyFunc 
//   ^-- error because MyFunc is narrower than typeof func2

Ett intuitivt sätt att tänka på detta är från perspektivet av konsumenten som anropar din funktion/React-komponent. I det fall där de förväntar sig att anropa en funktion av typeof f2, förväntar de sig att endast behöva skicka props av aNumber och ändå, genom att tilldela en snävare funktion till f2, skulle de nu också behöva skicka aString, vilket de inte förväntade sig.

Det här problemet dyker upp ofta i React av en förståelig anledning. Föreställ dig att du har en <Button>-komponent som tar en prop, och en <FancyButton>-komponent som tar flera props. Det är frestande att tänka, ja, om jag behöver en vanlig <Button> så fungerar en <FancyButton> lika bra. Det låter lite som enkel arv. Men om du tänker på det lite mer finns det ett fel. Om jag är en komponent som förväntar mig att behöva infoga en <Button> som ett av mina barn, betyder det att jag bara förväntar mig att behöva ge den en prop. Om jag nu byter ut den mot en <FancyButton> förväntas en massa props av den komponenten som jag inte förväntade mig att tillhandahålla.

Den intuitiva poängen är att du inte bara visar <Button>-komponenten - i så fall skulle du kunna strunta i att <FancyButton> har mer "flärd". Det är att du faktiskt behöver tillhandahålla input - props - till komponenten, och därför kommer en smalare komponent att kräva att du (föräldrakomponenten) tillhandahåller mer.

Samma logik gäller naturligtvis generellt för alla funktioner, men jag antar att det tenderar att komma upp oftare i React eftersom vi tenderar att tänka på komponenter som "objekt att visa".

Här är ett mer omfattande SO-svar på detta problem generellt (vilket är knepigt i början!): Skillnaden mellan kovarianta och kontravarianta positioner i Typescript

Relaterade artiklar