输入类型定义取决于输入类型的函数

时间:2020-11-02 12:22:07

标签: typescript

我看到TypeScript有点奇怪的情况。我试图创建一个最小的可复制示例,以便可以在TypeScript Playground中轻松对其进行测试。

  • 有一个名为BaseA的类型,根据其类型参数之一,它可以具有不同的结构。
  • 有一个名为DFunction的类型,它表示一个接收BaseA类型的对象的函数。
  • DFunction的返回类型应不同,具体取决于接收到的BaseA的类型。
type BaseA<P, Special=false> = {
    payload: P;
} & Special extends true ? {_special:true} : {};

type DFunction = {
    <A extends BaseA<any, false>>(a: A): A;
    <A extends BaseA<any, true>>(a: A): Promise<A>;
};

function test(
    d: DFunction,
    normalA: BaseA<{x:number}, false>,
    specialA: BaseA<{x:number}, true>
) {
    // Since we are passing a `specialA` to `d`, the return type should be a Promise.
    d(specialA).then(() => {});
}

Playground URL

由于TypeScript无法理解d(specialA)返回了Promise,因此该代码在最后一行出错。我该如何输入DFunction,使其返回类型取决于其输入类型?

我想指出的一件事是,该模式确实适用于更简单的示例:

type DFunction = {
    <A extends string>(a: A): A;
    <A extends number>(a: A): Promise<A>;
};

function test(
    d: DFunction
) {
    d(5).then(() => {}); // no errors
    const b = d("5"); // `b` is inferred to be of type `string`
}

1 个答案:

答案 0 :(得分:3)

问题是因为BaseA<P, true>BaseA<P, false>的子类型,并且因为接受超类型的重载签名出现在接受子类型的过载签名之前。当两个不同的重载签名均适用时,Typescript将不会选择“最具体的”签名;它只会选择找到的第一个。

因此,有两种解决方案:您可以像这样更改过载签名的顺序,

type DFunction = {
    <A extends BaseA<any, true>>(a: A): Promise<A>;
    <A extends BaseA<any, false>>(a: A): A;
};

或者您可以更改BaseA的定义,以使BaseA<P, true>不能分配给BaseA<P, false>,如下所示:

type BaseA<P, Special=false> = {
    payload: P;
} & (Special extends true ? {_special: true} : {_special?: undefined});

Playground Link