typescript - Ako môžem napísať všeobecný TypeGuard pre GraphQL uniony?


Chcel by som napísať univerzálny Type Guard pre GraphQL unie.

Povedzme, že máme dva typy A a B, a C je ich zjednotený typ. A má vlastnosť a, ale B ju nemá. Myslím, že by mohlo byť užitočné, keby sme mohli mať typový stráž ako tento:

xs.filter(isTypename("a")).map(x => x.a)

Snažil som sa chvíľu, ale stále som to nedokončil.

Je to možné dosiahnuť?

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

Odpoveď č. 1

Myslím, že ak očakávate, že budete používať diskriminované unijné typy, potom dosiahnete lepšie správanie, ak vaša používateľom definovaná ochrana typu vráti typ ako val is Extract<DiscriminatedUnionType, {discriminant: LiteralDiscriminant}> namiesto prienikov ako val is DiscriminatedUnionType & {discriminant: LiteralDiscriminant} (alebo toho s Omit<DiscriminatedUnionType , "discriminant"> & {discriminant: LiteralDiscriminant}, ktorý sa ani nezostaví, pretože kompilátor nemôže overiť, že je to podtyp DiscriminatedUnionType).

AKTUALIZÁCIA PRE TS3.9+: vyššie uvedený problém s prienikmi bol v TS3.9 výrazne zlepšený. Teraz sú prieniky redukované pomocou diskriminačných vlastností (pozri microsoft/TypeScript#36696), takže val is DiscriminatedUnionType & {discriminant: LiteralDiscriminant} teraz eliminuje nekompatibilné typy z diskriminovaného zväzku. Stále však odporúčam použiť Extract, pretože výstup je jednoduchší a nenesie so sebou prienik. KONIEC AKTUALIZÁCIE

Extract<T, U> je pomocný typ, ktorý používa distribuovaný podmienený typ na filtrovanie zjednotení. Hypoteticky by sa dali vytvoriť prieniky, ktoré by robili to isté, ale v TypeScripte prieniky nesúvisiacich objektových typov sa nezredukujú na never. Takže zatiaľ čo {foo: "a", a: string} & {foo: "b"} je ekvivalentné never (nikdy nedostanete hodnotu tohto typu), kompilátor ho v skutočnosti nezredukuje na never. Na druhej strane, Extract<{foo: "a", a: string}, {foo: "b"}> produkuje never. A potrebujete túto redukciu, pretože X | never sa redukuje na X.


Takže vyskúšajme nasledujúcu verziu typovej ochrany:

const isTypename = <T extends string>(typename: T) => <
  N extends { __typename: string }
>(
  node: N
): node is Extract<N, { __typename: T }> => {
  return node.__typename === typename;
};

v ktorom predikát návratového typu používa Extract.

Všimnite si, že som tiež presunul generický N (váš NodeT) do podpisu vracanej funkcie. To by malo pomôcť s odvodzovaním typov. Volanie isTypeName("a") samo o sebe nemá zjavné miesto pre odvodzovanie N. Ak by som urobil

const filterCb = isTypename("a"); 
xs.filter(filterCb); 

Vaša verzia by nakoniec mala N odvodené ako { __typename: string } namiesto C, a bohužiaľ návratový typ by bol node is never namiesto node is A, čo by bol problém.

Niekedy vám kontextové písanie môže zachrániť, takže

xs.filter(isTypename("a"));

mohlo by byť dosť kontextového typu na to, aby sa N odvodilo ako C a všetko by bolo skvelé. Ale všeobecne by som chcel, aby isTypeName("a") vrátilo generickú funkciu, v ktorej N určite nebude predčasne vyriešené.


Dobre, uvidíme, či to funguje:

const ys1 = xs.filter(isTypename("a")).map(x => x.a); // string[]

Vyzerá to dobre! Dúfam, že to pomôže. Veľa šťastia.

Odkaz na kód

Súvisiace články