这里的第一个问题是允许重载函数的实现签名比任何调用签名都宽松。在实现内部,编译器只检查实现签名。这意味着在您的函数内部,foo 和 bar 都独立于 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 知道 args 到 method() 要么是空元组,要么是一对 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,但忘记了bar 与foo 有任何关系。解决此问题的一种方法是不要将 foo 和 bar 拆分为单独的类型,而是在单个 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 ?
}
}