[我经常使用下面的示例中的代码,并且想知道是否存在一些无需进行显式类型声明就可以键入find
结果的聪明方法。
type Foo = { type: "Foo" };
type Goo = { type: "Goo" };
type Union = Foo | Goo;
const arr: Union[] = [];
const foo = arr.find(a => a.type === "Foo") as Foo;
如果忽略as Foo
类型断言,即使结果只能返回类型Union
,结果也将是Foo
类型。
在这样的示例中,最简单的固定find
类型以返回缩小的类型的方法是什么?
编辑:此问题也可能适用于filter
和其他类似方法。
Edit2:建议的类似问题的接受答案(Way to tell TypeScript compiler Array.prototype.filter removes certain types from an array?)表明,通过在find/filter
的谓词中使用类型保护,可以缩小返回值。
这种类型的保护功能应如何缩小受歧视的联合,例如区分字符串文字总是在type
键下吗?
如果您希望生成用于用户定义类型防护函数的生成器,以返回区分已区分联合的类型谓词,它可能看起来像这样:
function discriminate<K extends PropertyKey, V extends string | number | boolean>(
discriminantKey: K, discriminantValue: V
) {
return <T extends Record<K, any>>(
obj: T & Record<K, V extends T[K] ? T[K] : V>
): obj is Extract<T, Record<K, V>> =>
obj[discriminantKey] === discriminantValue;
}
如果我调用discriminate("type", "Foo")
,则结果是签名类似于<T>(obj: T)=>obj is Extract<T, {type: "Foo"}>
的函数。 (我说这是相似,因为实际的返回值将T
限制为仅以"type"
作为键,并且您可以将"Foo"
分配给它的值。)让我们看看它是如何工作的:
const foo = arr.find(discriminate("type", "Foo")); // Foo | undefined
const goos = arr.filter(discriminate("type", "Goo")); // Goo[]
看起来不错。如果您传递不适用的字段/值,则会发生以下情况:
const mistake1 = arr.find(discriminate("hype", "Foo")); // error!
// -------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Union is not assignable to Record<"hype", any>.
const mistake2 = arr.find(discriminate("type", "Hoo")); // error!
// -------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Union is not assignable to ((Foo | Goo) & Record<"type", "Hoo">)
好的,希望能有所帮助;祝你好运!
这是上面答案中的jcalz的代码,加上了否定和联合。
export function isDiscriminate<K extends PropertyKey, V extends string | number | boolean>(
discriminantKey: K, discriminantValue: V | V[]
) {
return <T extends Record<K, any>>(
obj: T & Record<K, V extends T[K] ? T[K] : V>
): obj is Extract<T, Record<K, V>> =>
Array.isArray(discriminantValue)
? discriminantValue.some(v => obj[discriminantKey] === v)
: obj[discriminantKey] === discriminantValue;
}
export function isNotDiscriminate<K extends PropertyKey, V extends string | number | boolean>(
discriminantKey: K, discriminantValue: V | V[]
) {
return <T extends Record<K, any>>(
obj: T & Record<K, V extends T[K] ? T[K] : V>
): obj is Exclude<T, Record<K, V>> =>
Array.isArray(discriminantValue)
? discriminantValue.some(v => obj[discriminantKey] === v)
: obj[discriminantKey] === discriminantValue;
}
和用法:
type A = { type: "A" };
type B = { type: "B" };
type C = { type: "C" };
type Union = A | B | C;
const arr: Union[] = [];
arr.find(isDiscriminate("type", "A")); // A
arr.find(isDiscriminate("type", ["A", "B"])); // A | B
arr.find(isNotDiscriminate("type", "A")); // B | C
arr.find(isNotDiscriminate("type", ["A", "B"])) // C