【问题标题】:Narrowing a type argument that is constrained to a union type in function bodies缩小在函数体中约束为联合类型的类型参数
【发布时间】:2021-05-04 05:59:20
【问题描述】:

我正在寻找关于联合类型缩小的具体行为。我有以下联合类型的“可订购”类型:

type Orderable = string | number

我使用这种类型作为函数类型参数的约束,如下:

function foo<T extends Orderable>(x: T, y: T): void {
    // ...
}

// x and y must be of the same type within T, 
// and this constraint works from the usage perspective:
foo('bar', 'baz') // OK
foo(21, 42)       // OK
foo('bar', 42)    // ERROR

我不确定我想要的是否在 Typescript 中完全可行,但我希望能够在应用于 T 实例的函数中使用类型缩小,而不是在变量 (@987654324 @ 和 y),其类型被限制为 T。为了向您展示我的意思,我希望能够做到以下几点:

function foo<T extends string | number>(x: T, y: T): void {
    if (typeof x === 'string') {
        // if x = string, then T = string, 
        // thus y = string, and can be assigned to z:
        const z: string = y
    }
}

但上面对z 的赋值给出了错误

Type 'T' is not assignable to type 'string'.
- Type 'string | number' is not assignable to type 'string'.
- - Type 'number' is not assignable to type 'string'.

换句话说,if 语句应该“证明”T = string,但它只证明了x extends string。尽管证明x 是一个字符串并不能证明y 的类型确实让有些 有意义,但考虑到foo('bar', 42) 抛出错误的使用示例,我仍然认为这很奇怪;导致该错误的类型检查和函数体内的类型检查都依赖于 foo 的相同函数签名。如何正确解释这种(对我来说出乎意料的)行为?有没有其他方法可以进行这种缩小范围?

提前致谢!

【问题讨论】:

    标签: typescript generics


    【解决方案1】:

    问题是你可以这样调用这个函数:

    foo<string | number>("bar", 5);
    

    这意味着你的T要么是number要么是string,并且第一个参数的最终类型独立于第二个参数的最终类型,它们必须是string | number的子类型.

    但是你可能更严格:

    function areStrings(v: [string, string] | [number, number]): v is [string, string] {
        return typeof v[0] === 'string';
    }
    
    function bar(...args: [string, string] | [number, number]) {
        if (areStrings(args)) {
            const a: string = args[0];
            const b: string = args[1];
        } else {
            const a: number = args[0];
            const b: number = args[1];
        }
    }
    

    这样你就不得不提供两个字符串或两个数字,但不能混合提供。

    不幸的是,我在这里看不到任何消除自定义类型保护的方法,似乎 typescript 还不够聪明(还)无法自行缩小元组类型。但是这种类型保护对我来说看起来非常合理。

    【讨论】:

    • 我没有考虑过显式类型参数,傻!看来我正在寻找某种“仅推理”类型的参数想法。这将是一个很好的功能。不幸的是,由于参数更多,我不能总是使用 rest 参数,而且我在其他泛型类型中依赖于 T 。但是你的回答让我走得很远,谢谢!
    • @JeffreyWesterkamp 这不仅仅是关于显式类型参数。兔子洞更深 :) 例如,这是打破foo 用法的另一种方法:function baz&lt;T extends string | number&gt;(a: T) { foo(a, 5) }
    • @JeffreyWesterkamp 此外,rest 参数现在比 typescript 4.0 之前更强大。阅读 typescript 4.0 发行说明中的​​ Variadic tuples 和 typescript 4.2 中的 Leading/Middle Rest Elements in Tuple Types,我相信您可以使用它们。
    猜你喜欢
    • 1970-01-01
    • 2021-11-01
    • 2013-05-13
    • 1970-01-01
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 2020-09-16
    • 2021-12-25
    相关资源
    最近更新 更多