有没有办法从函数内部的类型保护中保持联合类型的类型推断?

Is there a way to keep type inference in union types from type guards inside a function?

假设我有三种类型(和一种并集)

type A = {
    type: 'a'
    title: string
    description: string
}

type B = {
    type: 'b'
    title: string
}

type C = {
    type: 'c'
    description: string
}

type D = A | B | C

我知道我可以使用 === 运算符进行正确的类型推断

function logger(t: D) {
    if (t.type === 'a' || t.type === 'b') console.log(t.title) // no problems here
    if (t.type === 'a' || t.type === 'c') console.log(t.description) // no problems here
}

但是,是否可以编写一个效用函数:

function matches<T extends { type: string }>(t: T, types: T['type'][]) : boolean {
    return types.includes(t.type)
}

所以我可以毫无错误地做到这一点?

function logger(t: D) {
    if (matches(t, ['a', 'b'])) console.log(t.title) // Property 'title' does not exist on type D
    if (matches(t, ['a', 'c'])) console.log(t.description) // Property 'description' does not exist on type D
}

您可以将 matches() 写成 user-defined type guard function,以便编译器理解您对函数的 true/false 输出的意图,以缩小t 参数。

这是一种方法:

function matches<T extends { type: string }, K extends string>(
    t: T, types: K[]
): t is Extract<T, { type: K }> {
    const widenedTypes: readonly string[] = types;
    return widenedTypes.includes(t.type);
}

这是 generic both in T, the type of t, but also K, the union of string literal types of the elements of types. The return type is Extract<T, {type: K}>, which uses the Extract<T, U> utility type 以将联合类型 T 过滤为仅可分配给 {type: K} 的那些成分。

注意编译器会报错types.includes(t.type),因为types的元素比t.type窄。我处理这个问题的方法是首先(安全地)将 typesK[] 扩大到 readonly string[],然后在其上调用 includes()。有关详细信息,请参阅 及其答案。


让我们看看它是否有效:

function logger(t: D) {
    if (matches(t, ['a', 'b'])) console.log(t.title) // okay
    if (matches(t, ['a', 'c'])) console.log(t.description) // okay
}

看起来不错!

Playground link to code