如何实现TypeScript中针对特定泛型的条件可选回调?
实现仅在数组为
number[]时可选回调的sum函数 需求描述
需要实现一个sum函数,接收数组和回调函数,要求仅当数组类型为number[]时,回调函数才是可选的:
- 传入
number[]时,可选择不传回调,直接对数组元素求和; - 传入其他类型数组时,必须传入回调函数将元素转为数字后求和。
初始实现及问题
最初的实现代码如下,但存在明显缺陷:
function sum<T>(iter: T[], callback?: (arg: T) => number): number { return callback ? iter.reduce((sum, element) => sum + callback(element), 0) : iter.reduce((sum, x) => sum + Number(x), 0); // <- 想移除Number() }
问题点:
- 代码暗示任意类型
T都可以转为数字,不符合实际需求; - 当
T为number时,会执行冗余的Number()转换。
尝试的重载方案及问题
尝试用重载修复,但TypeScript无法识别"回调为undefined时,T一定是number"的类型关系:
type OptionalIfNumber<T, U> = T extends number ? U | undefined : U; export function sum(iter: number[], callback?: undefined): number; export function sum<T>(iter: T[], callback: (arg: T) => number): number; export function sum<T>(iter: T[], callback: OptionalIfNumber<T, (arg: T) => number>): number { return callback ? iter.reduce((sum, element) => sum + callback(element), 0) : iter.reduce((sum, x) => sum + x, 0); // <- 此处TypeScript报错 }
可行解决方案
你的需求完全可以实现,核心是利用重载明确区分调用场景,再通过类型断言或类型守卫缩小类型范围:
方案1:结合重载与类型断言
// 重载签名1:number[]时回调可选 export function sum(iter: number[], callback?: undefined): number; // 重载签名2:非number[]时必须传回调 export function sum<T>(iter: T[], callback: (arg: T) => number): number; // 实现签名 export function sum<T>(iter: T[], callback?: (arg: T) => number): number { if (callback) { return iter.reduce((sum, element) => sum + callback(element), 0); } else { // 重载约束确保此处iter必为number[],安全断言 return (iter as number[]).reduce((sum, x) => sum + x, 0); } }
解释:重载签名已经明确了两种调用场景,当走到else分支时,必然是传入了number[]且未传回调,因此类型断言是安全的,TypeScript不会再报错。
方案2:结合重载与自定义类型守卫
如果不想用类型断言,可以通过自定义类型守卫让TypeScript自动识别类型:
// 自定义类型守卫:判断是否为number[] function isNumberArray(arr: unknown): arr is number[] { return Array.isArray(arr) && arr.every(item => typeof item === 'number'); } // 重载签名 export function sum(iter: number[], callback?: undefined): number; export function sum<T>(iter: T[], callback: (arg: T) => number): number; // 实现签名 export function sum<T>(iter: T[], callback?: (arg: T) => number): number { if (callback) { return iter.reduce((sum, element) => sum + callback(element), 0); } else if (isNumberArray(iter)) { return iter.reduce((sum, x) => sum + x, 0); } // 重载约束确保不会走到此处,可抛出错误兜底 throw new Error('参数错误:非数字数组必须传入回调函数'); }
内容的提问来源于stack exchange,提问作者Antyos
相关产品推荐
相关产品推荐

