你真正需要做的就是让它工作
对于来电者
不是为了制造
joinEntities
或
fields
optional parameters
:
export type t_findOne = {
(carID: number): Promise<t_car_base>
(carID: number, joinEntities: true): Promise<t_car_joined>
<K extends keyof t_car_joined>(carID: number, fields: K[]): Promise<Pick<t_car_joined, K>>
}
findOne(1).then((res) => res.color)
// ^? (parameter) res: t_car_base
findOne(1, true).then((res) => res.registrationPlateNumber)
// ^? (parameter) res: t_car_joined
findOne(1, ['model']).then((res) => res.model)
// ^? (parameter) res: Pick<t_car_joined, "model">
理想情况下,当你写作时
overloads
,所有呼叫签名都不应重叠。也就是说,您希望没有函数调用与多个调用签名匹配。如果一个调用确实匹配了多个调用签名,那么TypeScript必须做出决定
哪一个
使用时,通常根据它们出现的顺序。。。但也有其他的启发式方法,比如有时TypeScript会选择“最窄”或“最具体”的重载(这有时会让人感到惊讶,根据
microsoft/TypeScript#39833
). 有时重叠是不可避免的,但你
应该
在可行的情况下避免它。
在你的情况下,当
join实体
和
领域
是可选的,然后是类似的调用
findOne(1)
比赛
全部的
您的通话签名。看起来TypeScript出于某种原因决定解析对第二个调用签名的调用。也许这是语言中的错误?但我们不需要担心,因为我们只需设置所需的参数就可以修复它。毕竟,您已经有了一个处理
findOne(1)
案例;你不需要另外两个调用签名来处理它,因为这只会给TypeScript一个机会去做你不想让它做的事情
findOne(1, true)
可以
只有
匹配返回的签名
t_car_joined
,以及
findOne(1, [â¯])
可以
只有
匹配返回的签名
Pick<t_car_joined, K>
没有歧义,一切正常。
现在,
为了实施
一个主要问题是,它显然需要
三
参数,但调用者最多只能传递两个。参数名称无关紧要,JavaScript不会通过以下方式将参数与参数匹配
名称
,只有通过
位置
.TypeScript,通过扩展,具有相同的功能。所以第三个
领域
实现中的参数是无用的。你需要把它改成这样
<K extends keyof t_car_joined>(
carID: number,
joinEntitiesOrFields?: true | K[],
) => {
return new Promise<t_car_base | t_car_joined | Pick<t_car_joined, K>>((resolve, reject) => {
if (Array.isArray(joinEntitiesOrFields)) {
resolve({} as Pick<t_car_joined, K>)
} else if (joinEntitiesOrFields) {
resolve({} as t_car_joined)
} else {
resolve({} as t_car_base)
}
})
}
TypeScript中的重载是
erased
以及编译为JavaScript时类型系统的其余部分。有人提议让重载在编译为JavaScript时做一些特殊的事情,比如
microsoft/TypeScript#3442
,但这些都被拒绝了(见
this comment
)因为它超出了TypeScript的范围。TypeScript不想成为JavaScript的语法糖。
从技术上讲,这超出了你所问问题的范围。但如果你希望你的代码在运行时能够正常工作,这一点非常重要。
至于将函数实现分配给时出现的错误
findOne
,这是因为TypeScript没有也不能根据其调用签名正确验证重载函数的实现。当你超载时
function
statement
,TypeScript检查东西
太松了
。这往往会减少编译器错误,但也可能错误地允许不安全的事情:
function findOne(carID: number): Promise<t_car_base>
function findOne(carID: number, joinEntities: true): Promise<t_car_joined>
function findOne<K extends keyof t_car_joined>(carID: number, fields: K[]): Promise<Pick<t_car_joined, K>>
function findOne<K extends keyof t_car_joined>(
carID: number,
joinEntitiesOrFields?: true | K[],
) {
return new Promise<t_car_base | t_car_joined | Pick<t_car_joined, K>>((resolve, reject) => {
if (!Array.isArray(joinEntitiesOrFields)) {
resolve({} as Pick<t_car_joined, K>)
} else if (joinEntitiesOrFields) {
resolve({} as t_car_joined)
} else {
resolve({} as t_car_base)
}
})
}
对于TypeScript来说,以“正确”的方式检查重载太复杂了,所以它没有。参见
microsoft/TypeScript#13235
.
另一方面,当你有一个
arrow function
并尝试将其分配给重载签名,TypeScript会进行检查
过于严格
。只有当箭头函数推断的类型适用时,它才允许
每一个
呼叫签名,这很少是真的。在您的示例中,TypeScript对您有时返回感到不安
选择<t_car_joined,K>
当其中一个呼叫签名期望
t_car_base
这检查太严格了。
所以它不会让你做不安全的事情,但也不会让你去做安全的事情。有一个请求
microsoft/TypeScript#47669
至少使其工作方式类似于函数语句,但就目前而言,如果将箭头函数分配给重载函数类型的变量,则可能会收到至少一个调用签名不匹配的错误消息,即使它们确实匹配。
目前,如果你想使用重载箭头函数,你必须通过有意放松类型检查来避免这个问题,例如使用
type assertion
:
const findOne = (<K extends keyof t_car_joined>(
carID: number,
joinEntitiesOrFields?: true | K[],
) => {
return new Promise<t_car_base | t_car_joined | Pick<t_car_joined, K>>((resolve, reject) => {
if (Array.isArray(joinEntitiesOrFields)) {
resolve({} as Pick<t_car_joined, K>)
} else if (joinEntitiesOrFields) {
resolve({} as t_car_joined)
} else {
resolve({} as t_car_base)
}
})
}) as t_findOne;
这类似于使用函数语句进行重载。是的,可能会出错并返回错误的东西,就像重载函数语句一样。这只是意味着你需要小心并测试你的函数实现。但至少你没有收到错误消息。
Playground link to code