TypeScript泛型重载传联合类型时无匹配重载及类型推断异常
问题现象
可在TypeScript Playground在线复现问题。
调用map函数时提示无匹配重载,回调参数x被TypeScript推断为number | undefined,不符合预期的纯number类型,复现代码如下:
declare const w: State<number> const y = map(w, x => x) // 此处报错 export type ResolvedState<T> = { readonly status: 'resolved' readonly data: T } export type RejectedState<T> = { readonly status: 'rejected' readonly error?: Error readonly data?: T } export type PendingState<T> = { readonly status: 'pending' readonly data?: T } export type State<T> = ResolvedState<T> | RejectedState<T> | PendingState<T> function map<T, U>(state: ResolvedState<T>, mapper: (data: T) => U): ResolvedState<U> function map<T, U>(state: RejectedState<T>, mapper: (data: T) => U): RejectedState<U> function map<T, U>(state: PendingState<T>, mapper: (data: T) => U): PendingState<U> function map<T, U>(state: State<T>, mapper: (data: T) => U): State<U> { switch (state.status) { case 'resolved': return { status: 'resolved', data: mapper(state.data), } case 'rejected': return { status: 'rejected', error: state.error, data: state.data === undefined ? undefined : mapper(state.data), } case 'pending': return { status: 'pending', data: state.data === undefined ? undefined : mapper(state.data), } } }
完整报错信息:
No overload matches this call. Overload 1 of 3, '(state: ResolvedState<number | undefined>, mapper: (data: number | undefined) => unknown): ResolvedState<unknown>', gave the following error. Argument of type 'State<number>' is not assignable to parameter of type 'ResolvedState<number | undefined>'. Type 'RejectedState<number>' is not assignable to type 'ResolvedState<number | undefined>'. Types of property 'status' are incompatible. Type '"rejected"' is not assignable to type '"resolved"'. Overload 2 of 3, '(state: RejectedState<number>, mapper: (data: number) => unknown): RejectedState<unknown>', gave the following error. Argument of type 'State<number>' is not assignable to parameter of type 'RejectedState<number>'. Type 'ResolvedState<number>' is not assignable to type 'RejectedState<number>'. Types of property 'status' are incompatible. Type '"resolved"' is not assignable to type '"rejected"'. Overload 3 of 3, '(state: PendingState<number>, mapper: (data: number) => unknown): PendingState<unknown>', gave the following error. Argument of type 'State<number>' is not assignable to parameter of type 'PendingState<number>'. Type 'ResolvedState<number>' is not assignable to type 'PendingState<number>'. Types of property 'status' are incompatible. Type '"resolved"' is not assignable to type '"pending"'.
原因分析
报错根源是现有三个重载签名都不支持接收联合类型State<T>作为入参:
- 三个重载分别只接收
ResolvedState<T>、RejectedState<T>、PendingState<T>三个独立类型,没有重载覆盖三者联合的State<T>类型场景 - TypeScript重载匹配是逐个校验签名,传入的
State<number>是三个子类型的联合,无法命中任何一个只接收单个子类型的重载,因此抛出无匹配重载的错误 - 回调参数被推断为
number | undefined是重载匹配失败后的兜底行为:TS会合并所有重载的参数类型,由于RejectedState和PendingState的data是可选属性,类型为T | undefined,合并后最终得到number | undefined
修复方法
把原本只作为实现的、接收State<T>联合类型的签名,补充为对外暴露的重载签名即可,调整后的代码如下:
// 新增该重载,覆盖传入联合类型State的场景 function map<T, U>(state: State<T>, mapper: (data: T) => U): State<U> function map<T, U>(state: ResolvedState<T>, mapper: (data: T) => U): ResolvedState<U> function map<T, U>(state: RejectedState<T>, mapper: (data: T) => U): RejectedState<U> function map<T, U>(state: PendingState<T>, mapper: (data: T) => U): PendingState<U> // 原有函数实现逻辑无需修改 function map<T, U>(state: State<T>, mapper: (data: T) => U): State<U> { switch (state.status) { case 'resolved': return { status: 'resolved', data: mapper(state.data), } case 'rejected': return { status: 'rejected', error: state.error, data: state.data === undefined ? undefined : mapper(state.data), } case 'pending': return { status: 'pending', data: state.data === undefined ? undefined : mapper(state.data), } } }
调整后调用map(w, x => x)时,TS会正确命中新增的联合类型重载,x的类型会被推断为number,返回值类型也会正确识别为State<number>,报错消失。
内容的提问来源于stack exchange,提问作者Stav Alfi
相关产品推荐
相关产品推荐

