是否可以在不详尽检查函数体中的每个参数的情况下缩小重载参数的类型?
Posted
技术标签:
【中文标题】是否可以在不详尽检查函数体中的每个参数的情况下缩小重载参数的类型?【英文标题】:Is it possible to narrow the types of overloaded parameters without exhaustively checking each parameter in the function body? 【发布时间】:2020-05-01 20:56:36 【问题描述】:我想定义一个函数,它可以接受以两种方式之一输入的参数。例如:
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');
但是重载的类型分组在函数内部似乎没有任何作用。
当只有几个参数时,很容易显式(且冗余地)对每个参数进行类型检查,或者在检查完一个参数后对每个参数使用类型断言,但这仍然很难看。当参数超过几个时,情况会变得更糟。
另一个有问题的冗余是每个可能的参数类型不仅需要在type
中列出,还需要在函数的参数列表中列出。例如,类型定义中的(abc: number)
和(abc: string)
也需要参数列表中的= (abc: number | string)
。
是否有更好的模式可用于函数重载而不完全放弃它?我知道至少有两种不涉及重载的解决方法:
传递 abc: number, def: string | abc: string
类型的对象而不是多个单独的参数,然后通过类型保护传递对象
对两种不同类型的参数使用两个单独的函数
但我宁愿使用重载如果有一个不错的方法来处理它。
【问题讨论】:
【参考方案1】:您的所有方法都是合理的:(1) 单独的函数声明 (2) 对象参数的联合或 (3) 类似问题的函数重载。
我更喜欢 (1),如果调用者已经有足够的信息来决定必须调用哪个函数,因为这会降低 fn
主体的整体条件复杂性。
(2) 对discriminated union type 更有意义,因此您不会对调用者进行过多的属性检查。 Example:
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')
此外,您不必在Fn
和fn
签名中列出两次类型。我在这里找到了 oldie 的原因:只有具有单个重载的函数才能应用上下文类型。
使用 (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
因此,您将始终必须使用带有可选参数和/或联合类型的更广泛的类型签名,并将这些类型缩小到函数体内。
【讨论】:
以上是关于是否可以在不详尽检查函数体中的每个参数的情况下缩小重载参数的类型?的主要内容,如果未能解决你的问题,请参考以下文章
是否可以在不手动将重写的克隆方法添加到 C++ 中的每个派生类的情况下克隆多态对象?