Vyhledávání na webu

reactjs - Je možné přidat typy vlastností (prop types) k vlastnostem komponenty React na základě přítomnosti nebo hodnoty jiné vlastnosti (prop)?


Mám poměrně složitou React komponentu v aplikaci založené na Typescriptu. Existuje více skupin props, které by měly být požadovány jen tehdy, když je přítomen/jistý jiný prop. Existuje nějaký způsob, jak toho dosáhnout v Typescriptu? Ještě jsem se do pokročilého typování s ním příliš nepustil.

Udělal jsem několik pokusů s diskriminovanými uniemi, ale nemyslím si, že to úplně pokrývá všechny možnosti, protože je možné, že bude vyžadována jakákoli kombinace nebo žádná z těchto dodatečných skupin vlastností.

Tohle je to, s čím tady pracuji.

interface CommonProps {
  ...a bunch of props...
}

interface ManagedProps extends CommonProps {
  managed: true;
  ...props for only when managed is present/true...
}

interface ServerSideProps extends CommonProps {
  serverSide: true;
  ...props for only when serverSide is present/true...
}

interface Props = ???

Základní pravidlo je, že CommonProps by se měly vztahovat na tuto komponentu. Pokud je managed true, pak by se měly vztahovat i ManagedProps. Pokud je serverSide true, pak se vztahují i ServerSideProps.
Tato situace u mě zatím nenastala, ale bylo by hezké, kdyby volitelné skupiny vlastností mohly překrýt vlastnost definovanou v CommonProps.

Odpověď č. 1

Měl jsem velký úspěch při používání pomocníka Union.Strict z ts-toolbelt.

Nejprve si definujme vaše dva samostatné soubory možných typů, spojme je do přísné unie a napišme typovou ochranu, která našemu React komponentu řekne, který soubor vlastností používáme. Rozšířím příklad, který jste použil ve svém původním příspěvku:

import { Union } from 'ts-toolbelt';

interface CommonProps {
  commonProp: string
}

interface ManagedProps extends CommonProps {
  managed: true;
  managedBoolean: boolean;
  managedNumber: number;
}

interface ServerSideProps extends CommonProps {
  serverSide: true;
  serverBoolean: boolean;
  serverNumber: number;
}

const isManaged = (props: Props): props is ManagedProps => {
  return props.managed;
}

type Props = Union.Strict<ManagedProps | ServerSideProps>

Teď napišme React komponentu, která využívá typovou ochranu, kterou jsme napsali:

const OurComponent: React.FC<Props> = (props) => {
  if (isManaged(props)) {
    return <div>Managed {props.commonProp}</div>
  } else {
    return <div>ServerSide {props.commonProp}</div>
  }
}

To způsobí chyby, pokud použijete nesprávné typy. Můžete se na to podívat v tomto StackBlitzu, který jsem vytvořil.

Odpověď č. 2

Mohl byste použít podmíněné typy, abyste získali něco takového. Například:

interface CommonProps {
    foo: string;
}

interface ManagedProps extends CommonProps {
    managed: true;
    managedOnlyProp: boolean;
}

interface ServerSideProps extends CommonProps {
    serverSide: true;
    serverOnlyProp: boolean;
}

type MappedProps<T> =
    T extends { managed: true } ? ManagedProps :
    T extends { serverSide: true } ? ServerSideProps :
    CommonProps;

// Example function to test this:

declare function takeProps<T>(props: T & MappedProps<T>): void;

// And:

// This is OK, neither flag is present, so CommonProps are used:
takeProps({ foo: "" })

// This is an error. The managed flag is present, so we must also include the managed only props:
takeProps({ foo: "", managed: true }) // Gives error: Property 'managedOnlyProp' is missing in type '{ foo: string; managed: true; }' but required in type 'ManagedProps'.

// But this is okay, the flag is present but set to false...
takeProps({ foo: "", managed: false })

// And likewise for the serverSide versions:
takeProps({ foo: "", serverSide: true }) // Gives error: Property 'serverOnlyProp' is missing in type '{ foo: string; serverSide: true; }' but required in type 'ServerSideProps'.
takeProps({ foo: "", serverSide: true, serverOnlyProp: false }) // OK, no errors
takeProps({ foo: "", serverSide: false }) // OK, no errors

Tady je odkaz na Playground.

Všimněte si, že existuje výhrada v tom, že to funguje pouze pro každý přepínač jednotlivě. Při použití v kombinaci však můžete stále předat neplatný objekt:

// This is wrong (missing serverOnlyProp) but no error is produced... :(
takeProps({foo: "", serverSide: true, managed: true, managedOnlyProp: false})

Související články