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

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

最佳答案

我想如果您希望使用 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 )。
TS3.9+ 更新 : 上述交叉点问题在 TS3.9 中得到了很大改善。现在,intersections are reduced by discriminant properties (见 microsoft/TypeScript#36696),所以 val is DiscriminatedUnionType & {discriminant: LiteralDiscriminant}现在将从受歧视的联合中消除不兼容的类型。我仍然推荐使用 Extract但是,因为输出更简单,并且不会在交叉点周​​围进行。 结束更新 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 (你永远不会得到那种类型的值),编译器实际上并没有将它减少到 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

关于typescript - 如何为 GraphQL 联合编写通用 TypeGuard?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57674872/

相关文章:

javascript - 为什么简单的 html 和 css 在 Angular 2 元素中的工作方式与 vanilla html 网站不同

angular - 如何从 Angular - ng2 文件上传打开输入类型 ="file"对话框

javascript - TypeError : this. form._updateTreeValidity 不是函数

typescript - 声明一个构造函数以从 TypeScript 中的 (keyof) 参数正确推断泛型类型

typescript - 以 es5 为目标时,如何将 Promises 与 TypeScript 结合使用?

typescript - 为什么 TypeScript 的 IterableIterator<> 和 Generator<> 泛型略有不同?

typescript - 是否可以在文字而不是接口(interface)上使用 `keyof` 运算符?

javascript - Typescript 编译成的 javascript "namespace"模式的名称是什么?

javascript - 定位对象内的数组 - Angular 2 中的 Observables

javascript - 如果用户登录使用 React 路由器重定向到仪表板?