【问题标题】:Why does typescript expect 'never' as function argument when retrieving the function type via generics?为什么打字稿在通过泛型检索函数类型时期望“从不”作为函数参数?
【发布时间】:2021-03-12 22:45:19
【问题描述】:

我目前正在尝试从 TS 2.6 切换到 3.4,但遇到了奇怪的问题。这以前有效,但现在它向我显示编译器错误:

type MyNumberType = 'never' | 'gonna';
type MyStringType = 'give' | 'you';
type MyBooleanType = 'up' 

interface Bar {
    foo(key: MyNumberType): number;
    bar(key: MyStringType): string;
    baz(key: MyBooleanType): boolean;
}

function test<T extends keyof Bar>(bar: Bar, fn: T) {
    let arg: Parameters<Bar[T]>[0];
    bar[fn](arg); // error here
}

错误如下:

Argument of type 'Parameters<Bar[T]>' is not assignable to parameter of type 'never'.
Type 'unknown[]' is not assignable to type 'never'.
Type '[number] | [string] | [boolean]' is not assignable to type 'never'.
Type '[number]' is not assignable to type 'never'.

Typescript Playground 告诉我函数的预期参数是“从不”类型:

我完全不认为这里会出错。只有一个函数参数,参数的类型通过Parameters 推断。为什么函数期望never

【问题讨论】:

    标签: typescript


    【解决方案1】:

    问题是编译器不明白argbar[fn]correlated。它将它们都视为不相关的联合类型,因此当大多数组合不是时,它期望 every combination of union constituents is possible

    在 TypeScript 3.2 中,您刚刚收到一条错误消息,指出 bar[fn] 没有调用签名,因为它是具有不同参数的函数的联合。我怀疑该代码的任何版本都可以在 TS2.6 中使用。当然,Parameters&lt;&gt; 的代码并不存在,因为直到 TS2.8 才引入条件类型。我尝试以与 TS2.6 兼容的方式重新创建您的代码,例如

    interface B {
        foo: MyNumberType,
        bar: MyStringType,
        baz:MyBooleanType
    }
    
    function test<T extends keyof Bar>(bar: Bar, fn: T) {
        let arg: B[T]=null!
        bar[fn](arg); // error here
    }
    

    tested in TS2.7,但它仍然给出错误。所以我会假设这段代码从来没有真正起作用过。

    关于never 问题:TypeScript 3.3 引入了support for calling unions of functions,要求参数是函数联合中参数的交集。在某些情况下这是一种改进,但在您的情况下,它希望参数是一堆不同字符串文字的交集,这些字符串文字被折叠到 never。这与以前的错误(“你不能这样称呼”)基本相同,但以更令人困惑的方式表示。


    处理这个问题最直接的方法是使用type assertion,因为在这种情况下你比编译器更聪明:

    function test<T extends keyof Bar>(bar: Bar, fn: T) {
        let arg: Parameters<Bar[T]>[0] = null!; // give it some value
        // assert that bar[fn] takes a union of args and returns a union of returns
        (bar[fn] as (x: typeof arg) => ReturnType<Bar[T]>)(arg); // okay
    }
    

    类型断言是不安全的,这确实让你对编译器撒谎:

    function evilTest<T extends keyof Bar>(bar: Bar, fn: T) {
        // assertion below is lying to the compiler
        (bar[fn] as (x: Parameters<Bar[T]>[0]) => ReturnType<Bar[T]>)("up"); // no error!
    }
    

    所以你应该小心。有一种方法可以对此进行完全类型安全的版本,强制编译器对所有可能性进行代码流分析:

    function manualTest<T extends keyof Bar>(bar: Bar, fn: T): ReturnType<Bar[T]>;
    // unions can be narrowed, generics cannot
    // see https://github.com/Microsoft/TypeScript/issues/13995
    // and https://github.com/microsoft/TypeScript/issues/24085
    function manualTest(bar: Bar, fn: keyof Bar) {
        switch (fn) {
            case 'foo': {
                let arg: Parameters<Bar[typeof fn]>[0] = null!
                return bar[fn](arg);
            }
            case 'bar': {
                let arg: Parameters<Bar[typeof fn]>[0] = null!
                return bar[fn](arg);
            }
            case 'baz': {
                let arg: Parameters<Bar[typeof fn]>[0] = null!
                return bar[fn](arg);
            }
            default:
                return assertUnreachable(fn);
        }
    }
    

    但这太脆弱(如果您向Bar 添加方法,则需要更改代码)和重复(一遍又一遍的相同子句),我通常更喜欢上面的类型断言。

    好的,希望对您有所帮助;祝你好运!

    【讨论】:

    • 在不使用可区分联合的情况下,打字稿是否无法推断出泛型类型的真实分支?
    • 不幸的是not
    • 很抱歉对以前的什么工作有点不清楚。显然我没有使用Parameters,但最后“它以前工作过”我的意思是它没有显示任何错误,而不是它提供了正确的类型(这无论如何都是愚蠢的)。我也不是类型断言的朋友,但在这种情况下,它比使用冗余语句填充实际代码执行要好。
    【解决方案2】:

    barFn 的声明方式等价于:

    type Foo = (key: number) => number;
    type Bar = (key: string) => string;
    type Baz = (key: boolean) => boolean;
    
    function test1(barFn: Foo | Bar | Baz) {    
        barFn("a"); // error here
    }
    

    barFn 的参数将是类型的intersection 而不是unionnever 类型是底部类型,它的发生是因为 numberstringboolean 之间没有交集。

    理想情况下,您希望将barFn 声明为三种函数类型的交集。例如:

    function test2(barFn: Foo & Bar & Baz) {    
        barFn("a"); // no error here
    }
    

    【讨论】:

    • 这并没有真正解决以通用方式访问接口的函数参数类型的问题。 keyof Bar 首先正确地给了我一个联合类型,其中包含Bar 的所有属性名称(我真正想要的),Parameters&lt;Bar[T]&gt;[0] 正确地给了我指定函数的第一个参数的类型,这也有效。不幸的是,我自己无法传递函数,这就是为什么这不是我的问题的答案
    • @GernotRaudner 是的,我知道我会尝试使用泛型找到解决方案,但您的问题标题具有误导性:p
    • 对不起,我更新了问题标题,希望现在更清楚了。我还更新了示例以更具体地了解要求
    猜你喜欢
    • 2022-11-11
    • 2020-03-22
    • 2020-03-17
    • 2023-03-30
    • 2021-01-03
    • 2021-04-10
    • 2019-07-05
    • 2021-10-27
    • 2023-02-22
    相关资源
    最近更新 更多