Sno*_*now 5 overloading typescript
我想定义一个函数,它可以接受以两种方式之一输入的参数。例如:
type Fn = {
(abc: number, def: string): void,
(abc: string): void,
};
Run Code Online (Sandbox Code Playgroud)
给定此类型签名,如果abc是数字,则为def字符串,如果abc是字符串,则def未定义。这对于人类来说是很清楚的,但是 Typescript 有没有办法识别它呢?例如,以下实现失败:
const fn: Fn = (abc: number | string, def?: string) => {
if (typeof abc === 'string') console.log(abc.includes('substr'));
else console.log(def.includes('substr'));
}
Run Code Online (Sandbox Code Playgroud)
因为虽然 的类型abc已经缩小,但是 TS 并不知道 的类型def也已经确定,所以def.includes是不允许的。函数的调用者可以识别参数类型的分组,因此正如预期的那样,禁止执行以下操作:
fn('abc', 'def');
Run Code Online (Sandbox Code Playgroud)
但重载类型分组似乎在函数内部没有效果。
当只有几个参数时,很容易显式(且冗余地)对每个参数进行类型检查,或者在检查完每个参数后对每个参数使用类型断言,但这仍然很丑陋。当参数超过几个时,情况会变得更糟。
另一个有问题的冗余是,每个可能的参数类型不仅需要在 中列出type,而且还需要在函数的参数列表中列出。例如(abc: number),(abc: string)在类型定义中也需要= (abc: number | string)在参数列表中。
是否有更好的模式可用于函数重载而不完全放弃它?我知道至少有两种不涉及重载的解决方法:
传递一个类型的对象{ abc: number, def: string } | { abc: string }而不是多个单独的参数,然后将该对象传递给类型保护
对两种不同类型的参数使用两个单独的函数
但如果有合适的方法来处理它,我宁愿使用重载。
您的所有方法都可以是合理的:(1)单独的函数声明(2)对象参数的联合或(3)像所讨论的那样的函数重载。
我更喜欢(1),如果调用者已经有足够的信息来决定必须调用哪个函数,因为这会降低fn主体的整体条件复杂性。
(2) 对于有区别的联合类型更有意义,因此您不会为调用者丢失多余的属性检查。例子:
type OverloadParam =
| { kind: "a", abc: number; def: string }
| { kind: "b"; abc: string }
type Fn = (arg: OverloadParam) => void
const fn: Fn = (args) => {
if (args.kind === "a") {
args.def.includes('substr')
} else {
args.abc.includes('substr')
}
}
Run Code Online (Sandbox Code Playgroud)
此外,您不必在签名中Fn和作为签名的一部分两次列出类型fn。我在这里发现这个老东西作为原因:只有具有单个重载的函数才能应用上下文类型。
对于(3),没有巧妙的方法来处理函数内部的可变函数参数。TS/JS 不支持这样的函数重载实现:
function fn(abc: number, def: string): void { }
function fn(abc: string): void { }
// error (TS): Duplicate function implementation. JS would overwrite the first declaration
Run Code Online (Sandbox Code Playgroud)
因此,您始终必须使用带有可选参数和/或联合类型的更宽类型签名,并在函数体内缩小这些类型的范围。