为什么打字稿在通过泛型检索函数类型时期望“从不"作为函数参数? [英] Why does typescript expect 'never' as function argument when retrieving the function type via generics?

查看:25
本文介绍了为什么打字稿在通过泛型检索函数类型时期望“从不"作为函数参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm currently trying to switch from TS 2.6 to 3.4 and I'm running into weird problems. This previously worked, but now it's showing me a compiler error:

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 告诉我函数的预期参数是never"类型:

The Typescript playground tells me that the expected argument of the function is of type 'never':

我不希望这里有任何错误.只有一个函数参数,参数的类型通过 Parameters 推断出来.为什么函数期望never?

I don't expect an error here at all. There is only one function argument, and the type of the argument gets inferred via Parameters. Why is does the function expect never?

推荐答案

问题是编译器不理解 argbar[fn]相关.它将它们都视为不相关的联合类型,因此期望 联合的每个组合成分是可能的,而大多数组合都不是.

The problem is that the compiler does not understand that arg and bar[fn] are correlated. It treats both of them as uncorrelated union types, and thus expects that every combination of union constituents is possible when most combinations are not.

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

In TypeScript 3.2 you'd've just gotten an error message saying that bar[fn] doesn't have a call signature, since it is a union of functions with different parameters. I doubt that any version of that code worked in TS2.6; certainly the code with Parameters<> wasn't in there since conditional types weren't introduced until TS2.8. I tried to recreate your code in a TS2.6-compatible way like

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
}

测试在 TS2.7 中,但它仍然给出错误.所以我假设这段代码从来没有真正起作用.

and tested in TS2.7 but it still gives an error. So I'm going to assume that this code never really worked.

至于 never 问题:TypeScript 3.3 引入了 通过要求参数是函数联合中参数的交集来支持调用函数联合.这在某些情况下是一种改进,但在您的情况下,它希望参数是一堆不同字符串文字的交集,它会被折叠为 never.这基本上与之前相同的错误(你不能称之为")以更令人困惑的方式表示.

As for the never issue: TypeScript 3.3 introduced support for calling unions of functions by requiring that the parameters be the intersection of the parameters from the union of functions. That is an improvement in some cases, but in your case it wants the parameter to be the intersection of a bunch of distinct string literals, which gets collapsed to never. That's basically the same error as before ("you can't call this") represented in a more confusing way.

最直接的方法是使用 类型断言,因为在这种情况下你比编译器更聪明:

The most straightforward way for you to deal with this is to use a type assertion, since you are smarter than the compiler in this case:

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
}

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

A type assertion is not safe, you this does let you lie to the compiler:

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!
}

所以你应该小心.有一种方法可以做一个完全类型安全的版本,迫使编译器对每一种可能性进行代码流分析:

So you should be careful. There is a way to do a completely type safe version of this, forcing the compiler to do code flow analysis on every possibility:

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 添加方法,则需要更改代码)和重复(一遍又一遍的相同子句),我通常更喜欢上面的类型断言.

But that is so brittle (requires code changes if you add methods to Bar) and repetitive (identical clauses over and over) that I usually prefer the type assertion above.

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

Okay, hope that helps; good luck!

这篇关于为什么打字稿在通过泛型检索函数类型时期望“从不"作为函数参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆