如何为GraphQL联合编写通用TypeGuard?

时间:2019-08-27 12:37:10

标签: typescript

我想为GraphQL联合编写一种全能型Type Guard。

假设我们有两种类型A和B,而C是它们的并集类型。 A具有属性a,但B没有。 我认为,如果我们可以有这样的类型保护,那就很有用:

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

我尝试了一段时间,但仍然没有完成。

可以实现吗?

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

1 个答案:

答案 0 :(得分:1)

我认为,如果您希望使用discriminated union类型,那么如果用户定义的类型防护返回的是val is Extract<DiscriminatedUnionType, {discriminant: LiteralDiscriminant}>这样的类型,而不是val is DiscriminatedUnionType & {discriminant: LiteralDiscriminant}这样的交集,则会得到更好的行为(或带有Omit<DiscriminatedUnionType , "discriminant"> & {discriminant: LiteralDiscriminant}的代码,甚至因为DiscriminatedUnionType的编译器can't verify that it's a subtype而无法编译。

Extract<T, U>utility type,它使用distributive conditional type来过滤联合。假设,可以使用TypeScript intersections of disjoint object types do not reduce to never来使交集做相同的事情。因此,虽然{foo: "a", a: string} & {foo: "b"}never 等效(您永远不会得到该类型的值),但编译器实际上并未 reduce never。另一方面,Extract<{foo: "a", a: string}, {foo: "b"}> 产生never。而且您需要进行缩减,因为X | never缩减为X


所以让我们尝试以下类型的类型防护:

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

其中返回类型谓词使用Extract

请注意,我还将通用N(您的NodeT)移到了返回函数的签名中。这应该有助于类型推断。调用isTypeName("a")本身没有明显的N推断站点。如果我做了

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

您的版本最终将N推断为{ __typename: string }而不是C,很遗憾,返回类型将是node is never而不是node is A,这将是一个问题。

有时候contextual typing可以拯救您,所以

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

可能是足够的上下文类型,可以将N推断为C,并且一切都会很好。但是通常我希望isTypeName("a")返回一个泛型函数,其中肯定不会过早解决N


好的,让我们看看它是否有效:

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

看起来不错!希望能有所帮助。祝你好运。

Link to code