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})
