繁体   English   中英

为什么打字稿在通过泛型检索函数类型时期望“从不”作为函数参数?

[英]Why does typescript expect 'never' as function argument when retrieving the function type via generics?

我目前正在尝试从 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 操场告诉我该函数的预期参数是“从不”类型: 在此处输入图片说明

我根本不希望这里有错误。 只有一个函数参数,参数的类型通过Parameters推断。 为什么函数 expect never

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

在 TypeScript 3.2 中,您刚刚收到一条错误消息,指出bar[fn]没有调用签名,因为它是具有不同参数的函数的联合。 我怀疑该代码的任何版本都适用于 TS2.6; 当然,带有Parameters<>的代码并不在那里,因为条件类型直到 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
}

在 TS2.7 中进行了测试,但仍然出现错误。 所以我将假设这段代码从未真正起作用。

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


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

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

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

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而不是union 该类型never不是底部类型,它发生是因为numberstringboolean之间没有交集。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM