Typescript 重载、可选参数和类型推断

Typescript overloads, optional arguments and type inference

我目前正在研究 Typescript 中的重载。

假设我有一个重载函数:

function method(): void;
function method(foo: boolean, bar: boolean): void;
function method(foo?: boolean, bar?: boolean) {
    if (foo === true || foo === false) {
        const result = bar;
    }
}

要么不带参数调用函数,要么带两个参数(foo 和 bar)调用函数。根据 vscode 的智能感知,result 变量的类型为 boolean | undefined

我测试了foo参数,为什么bar会变成undefined?如果 foo 存在,类型推断不应该预测 bar 也存在吗?

这里的第一个问题是重载函数的实现签名允许比任何调用签名更宽松。在实现内部,编译器只检查实现签名。这意味着在您的函数内部,foobar 都独立于类型 boolean | undefined 并且无法恢复调用该方法的任何人都将指定两者或两者都不指定的事实。

TypeScript 最近添加了对 rest/spread tuples in function parameters 的支持,因此您可以像这样重写函数签名:

declare function method(...args: [] | [boolean, boolean]);   
method(); // okay
method(false); // errror
method(true, false); // okay

现在 TypeScript 知道 argsmethod() 是空元组或一对 boolean 值。如果你愿意,你可以保留重载,只是让实现签名更窄:

function method(): void;
function method(foo: boolean, bar: boolean): void;
function method(...args: [] | [boolean, boolean]) {
  const foo = args[0];
  const bar = args[1];
  if (foo === true || foo === false) {
    const result = bar; // oops, still boolean | undefined
  }
}

不幸的是,推理仍然不起作用,这是第二个问题:TypeScript 的控制流分析根本没有我们聪明。虽然 我们 知道 foo 的类型与 bar 的类型相关,但编译器却不知道。如果缩小foo却忘了barfoo有什么关系。解决此问题的一种方法是不将 foobar 拆分为不同的类型,而是在单个 args 变量上使用 属性 访问类型保护。当 args[] | [boolean, boolean] 缩小到 [boolean, boolean] 时,您可以确定第二个元素已定义:

function method(): void;
function method(foo: boolean, bar: boolean): void;
function method(...args: [] | [boolean, boolean]) {    
    if ('0' in args) {
        const result = args[1]; // boolean
    }
}

这可能是代码更改太多,而 IntelliSense 对您来说不值得。如果是这样,并且您愿意让编译器变得更聪明,那么您可以使用 type assertion 并继续您的一天:

function method(): void;
function method(foo: boolean, bar: boolean): void;
function method(foo?: boolean, bar?: boolean) {
    if (foo === true || foo === false) {
        const result = bar as boolean; // I'm smarter than the compiler 
    }
}