Typescript 是否可以在不彻底检查函数体中的每个参数的情况下缩小重载参数的类型?

Typescript 是否可以在不彻底检查函数体中的每个参数的情况下缩小重载参数的类型?,typescript,overloading,Typescript,Overloading,我想定义一个函数,它可以接受以两种方式之一键入的参数。例如: type Fn = { (abc: number, def: string): void, (abc: string): void, }; 给定此类型签名,如果abc是一个数字,则def是一个字符串,如果abc是一个字符串,则未定义def。这对人类来说是清楚的,但是Typescript有没有办法识别它呢?例如,以下实现失败: const fn: Fn = (abc: number | string, def?: st

我想定义一个函数,它可以接受以两种方式之一键入的参数。例如:

type Fn = {
    (abc: number, def: string): void,
    (abc: string): void,
};
给定此类型签名,如果
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'));
}
因为尽管
abc
的类型已经缩小,TS不理解
def
的类型也已经确定,所以不允许
def.includes
。函数的调用者可以识别参数类型的分组,因此如下所示是禁止的:

fn('abc', 'def');
但是重载类型分组在函数内部似乎没有任何效果

当只有几个参数时,显式地(冗余地)检查每个参数的类型,或者在检查完每个参数后为每个参数使用类型断言是很容易的,但这仍然很难看。当存在多个参数时,情况会变得更糟

另一个有问题的冗余是,每个可能的参数类型不仅需要在
类型中列出,还需要在函数的参数列表中列出。例如,类型定义中的
(abc:number)
(abc:string)
也要求参数列表中的
=(abc:number | string)

有没有更好的模式可以用于函数重载而不完全抛弃它?我知道至少有两种解决方法不涉及重载:

  • 传递类型为
    {abc:number,def:string}{abc:string}
    的对象,而不是多个单独的参数,然后通过类型保护传递对象

  • 对两种不同类型的参数使用两个单独的函数


但是如果有合适的处理方法,我宁愿使用重载。

您所有的方法都是合理的:(1)单独的函数声明(2)对象参数的并集或(3)类似于所讨论的函数重载

如果调用者已经有足够的信息来决定调用哪个函数,我更喜欢(1),因为这降低了
fn
body的整体条件复杂性

(2) 使用a更有意义,因此不会对调用方进行过多的属性检查:

此外,您不必在
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
因此,您必须始终使用带有可选参数和/或联合类型的更广泛的类型签名,并在函数体中缩小这些类型的范围

function fn(abc: number, def: string): void { }
function fn(abc: string): void { } 
// error (TS): Duplicate function implementation. JS would overwrite the first declaration