我想为 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/