我想为GraphQL联合编写一种全能型Type Guard。
假设我们有两种类型A和B,而C是它们的并集类型。
A具有属性a
,但B没有。
我认为,如果我们可以有这样的类型保护,那就很有用:
xs.filter(isTypename("a")).map(x => x.a)
我尝试了一段时间,但仍然没有完成。
可以实现吗?
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);
答案 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[]
看起来不错!希望能有所帮助。祝你好运。