我正在尝试将已区分的联合数组过滤为其联合类型的单独数组。我偶然发现了this question它给出了通用的鉴别器。
不幸的是,我正在使用的数据在最高级别没有区分键。
type Foo = { meta: {type: "Foo"} };
type Goo = { meta: {type: "Goo"} };
type Union = Foo | Goo;
我通过更改另一篇文章中的鉴别器函数来修复此问题,以期望“元”对象降低一级。
function discriminateNested<K extends PropertyKey, V extends string | number | boolean>(
discriminantKey: K, discriminantValue: V
) {
return <T extends Record<PropertyKey, any>>(
obj: T & Record<"meta", Record<K, V extends T["meta"][K] ? T["meta"][K] : V>>
): obj is Extract<T, Record<"meta", Record<K, V>>> =>
obj["meta"][discriminantKey] === discriminantValue;
}
有没有更通用的方法来做到这一点?也许通过使用像
discriminateNested("meta.type", "Foo")
这样的字符串指定嵌套键?
一种可能的方法是
discriminateNested()
获取键的 tuple 并根据相应嵌套属性的值进行区分。你可以用点分隔的字符串来代替,但这种方式在打字方面更直接一些。
首先我们应该定义
NestedRecord<K, V>
,其中K
是一个键元组。我们希望 NestedRecord<["a", "b", "c"], string>
等同于 {a: {b: {c: string}}}
。它可能看起来像这样:
type NestedRecord<K extends PropertyKey[], V> =
K extends [infer K0 extends PropertyKey, ...infer KR extends PropertyKey[]] ?
{ [P in K0]: NestedRecord<KR, V> } : V;
K
并构建嵌套类型。
现在
discriminatedNested()
可以这样实现:
function discriminateNested<K extends PropertyKey[], V extends string | number | boolean>(
discriminantKeys: [...K], discriminantValue: V
) {
return <T extends NestedRecord<K, string | number | boolean>>(
obj: T
): obj is Extract<T, NestedRecord<K, V>> =>
discriminantKeys.reduce<any>((acc, k) => acc[k], obj) === discriminantValue;
}
这需要
discriminantKeys
的元组类型 K
(好吧,[...K]
,这只是暗示你希望它是一个元组而不是无序数组;参见 microsoft/TypeScript#39094 的描述,它实现了可变参数元组类型)和 discriminant value
类型的 V
,并返回自定义类型保护函数,其中 Extracts
输入类型 T
的联合成员仅分配给那些可分配给 Nestedrecord<K, V>>
的实现在reduce()
上使用
discriminantKeys
数组方法来执行嵌套索引。
让我们测试一下:
type Foo = { meta: { type: "Foo" }, a: string };
type Goo = { meta: { type: "Goo" }, b: number };
type Union = Foo | Goo;
const discFoo = discriminateNested(["meta", "type"], "Foo");
/* const discFoo: <T extends {
meta: {
type: string | number | boolean;
};
}>(obj: T) => obj is Extract<T, {
meta: {
type: "Foo";
};
}> */
const u: Union = Math.random() < 0.5 ?
{ meta: { type: "Foo" }, a: "abc" } : { meta: { type: "Goo" }, b: 123 };
if (discFoo(u)) {
u // Foo
console.log(u.a.toUpperCase());
} else {
u // Goo
console.log(u.b.toFixed(2));
}
看起来不错。
discFoo
类型的保护函数接受任何具有 {meta:{type: string | number | boolean}}
类型的输入,并过滤掉那些可分配给 {meta:{type:"Foo"}}
的联合成员。当你给它传递一个Union
类型的值时,真分支缩小到Foo
,假分支缩小到Goo
,根据需要。