typescript - Hvordan kan jeg skrive en generell TypeGuard for GraphQL-unions?
Jeg vil gjerne skrive en universell Type Guard for GraphQL-unioner.
La oss si at vi har to typer A og B, og C er samlingstypen av dem. A har en egenskap a, men B har det ikke. Jeg tror det kan være nyttig hvis vi kunne ha en typevern som dette:
xs.filter(isTypename("a")).map(x => x.a)Jeg prøvde en stund, men jeg fullførte fortsatt ikke.
Er det mulig å oppnå?
https://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgILIN4ChnIPp5gCeADhCHALYQBcyARHPQNw7Jx0DOYUoA5qwC+WUJFiIUAIUxsCxMhWp16AIxZsVXHvyFYs8lAGFkAXjTIAPskmt9pKXE4ozGYVggAPEgHsoYZAjeINzIwJwAKvaKzsgAPOHInpAgACacyNy8IHwANMgAct4pEAlJ5GmYclFUtBna2YIAfAAUbAbRdOFYAJSmjcjNIEW1hcXh3XRDxaHpAPKUwGCxoyV59FUKNfT9AGSY+ITVSsgJgn0yuFAQYACuUCDIUxAAdBvkNaYmZu01QraBwX8Hk4dEMAG0ALqmZBgjAHH7HRj0PIcBhMZCCPJwt4dBhqPKaPH0DEQ-5BEJETgARmhwOeMGAABsxM0wpFNtRmkjut1npQ4CRmh5zh5nnBuqwgA
interface A {
__typename: "a";
a: string;
}
interface B {
__typename: "b";
b: string;
}
type C = A | B;
type Base = {}
export const isTypename = <T extends string, NodeT extends {__typename: string}>(
typename: T
) => (node: NodeT): node is Omit<NodeT, "__typename"> & { __typename: T } => {
return node.__typename === typename;
};
const xs: C[] = [{ __typename: "a", a: "a" }, { __typename: "b", b: "b" }];
const ys1 = xs.filter(isTypename("a")).map(x => x.a);
Svar #1
Jeg tror at hvis du forventer å bruke diskriminerte unionstyper, så vil du få bedre oppførsel hvis din brukerdefinerte typevakt returnerer en type som val is Extract<DiscriminatedUnionType, {discriminant: LiteralDiscriminant}> i stedet for snitt som val is DiscriminatedUnionType & {discriminant: LiteralDiscriminant} (eller den med Omit<DiscriminatedUnionType , "discriminant"> & {discriminant: LiteralDiscriminant}, som ikke engang vil kompilere fordi kompilatoren ikke kan verifisere at det er en underklasse av DiscriminatedUnionType).
OPPDATERING FOR TS3.9+: problemet ovenfor med krysningspunkter har blitt forbedret betydelig i TS3.9. Nå reduseres krysningspunkter ved hjelp av diskriminant-egenskaper (se microsoft/TypeScript#36696), så val is DiscriminatedUnionType & {discriminant: LiteralDiscriminant} vil nå eliminere inkompatible typer fra den diskriminerte unionen. Jeg anbefaler fortsatt å bruke Extract, fordi resultatet da blir enklere og ikke inkluderer krysningspunktet. SLUTT OPPDATERING
Extract<T, U> er en hjelpetyper som bruker en distribuerbar betinget type for å filtrere forenede typer. Hypotetisk sett kunne snitt lages for å gjøre det samme, men i TypeScript reduseres ikke snitt av separate objekttyper til never. Så selv om {foo: "a", a: string} & {foo: "b"} er ekvivalent med never (du vil aldri få en verdi av den typen), reduserer kompilatoren det ikke faktisk til never. På den annen side vil Extract<{foo: "a", a: string}, {foo: "b"}> produsere never. Og du trenger denne reduksjonen, siden X | never reduseres til X.
Så la oss prøve følgende versjon av typevakten:
const isTypename = <T extends string>(typename: T) => <
N extends { __typename: string }
>(
node: N
): node is Extract<N, { __typename: T }> => {
return node.__typename === typename;
};
der returtypen predikat bruker Extract.
Merk at jeg også flyttet den generiske N (din NodeT) til signaturen til den returnerte funksjonen. Det burde hjelpe med typeimplisitthet. Kallet isTypeName("a") i seg selv har ingen åpenbar implisitt type for N. Hvis jeg gjorde
const filterCb = isTypename("a");
xs.filter(filterCb);
Din versjon ville ende opp med å ha N utledet som { __typename: string } i stedet for som C, og dessverre ville returtypen være node is never i stedet for node is A, noe som ville være et problem.
Noen ganger kan kontekstuelt skriving redde deg, så
xs.filter(isTypename("a"));
kan være nok kontekstuell type til å slutte at N er C og alt ville være flott. Men generelt vil jeg at isTypeName("a") skal returnere en generell funksjon der N definitivt ikke blir løst for tidlig.
OK, la oss se om det fungerer:
const ys1 = xs.filter(isTypename("a")).map(x => x.a); // string[]
Ser bra ut! Håper det hjelper. Lykke til.
Lenke til kode
