如何为自定义TypeScript Reduce函数实现条件类型以消除类型错误
自定义Array.reduce函数的TypeScript类型错误解决
问题描述
我实现了一个独立于全局Array的自定义Array.prototype.reduce函数,定义了三个重载签名:
function reduce<T> ( arr: T[], reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T, ): T; function reduce<T> ( arr: T[], reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T, initialValue: T, ): T; function reduce<T, R> ( arr: T[], reduceFunction: (acc: R, item: T, index: number, arr: T[]) => R, initialValue?: R ): R;
但编写函数体时,prev变量出现类型赋值错误,需要对initialValue和acc参数进行条件类型定义来消除这些错误。
函数体代码及错误提示如下:
function reduce<T> ( arr: T[], reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T, ): T; function reduce<T> ( arr: T[], reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T, initialValue: T, ): T; function reduce<T, R> ( arr: T[], reduceFunction: (acc: R, item: T, index: number, arr: T[]) => R, initialValue?: R ): R; function reduce<T, R> ( arr: T[], reduceFunction: (acc: R | T, item: T, index: number, arr: T[]) => R | T, initialValue?: R | T ): R | T { if (initialValue === undefined) { let prev = arr[0] for (let i = 1; i < arr.length; i++) { const item = arr[i] // Type 'T | R' is not assignable to type 'T'.'T' could be instantiated with an arbitrary type which could be unrelated to 'T | R'.ts(2322) prev = reduceFunction(prev, item, i, arr) } return prev } else { let prev = initialValue as R for (let i = 0; i < arr.length; i++) { const item = arr[i] // let prev: (T & ({} | null)) | (R & ({} | null)) // Type 'T | R' is not assignable to type '(T & ({} | null)) | (R & ({} | null))'. // Type 'T' is not assignable to type '(T & ({} | null)) | (R & ({} | null))'. // Type 'T' is not assignable to type 'T & null'. // Type 'T' is not assignable to type 'null'.ts(2322) prev = reduceFunction(prev, item, i, arr) as R } return prev } } // 使用示例 interface User { name: string; surname: string; } interface UserWithFullname extends User { fullname: string; } const arr: User[] = [{ name: "John", surname: "Doe" }]; const products = [{ capacity: 2, price: 10 }]; const result = reduce<User, UserWithFullname[]>( arr, (acc, item) => { return [...acc, { ...item, fullname: item.name + ' ' + item.surname }] }, [] ) const total = reduce( products, (acc, item) => { return acc + item.capacity * item.price }, 0 ) console.log(result) console.log(total)
解决方法
核心问题是函数实现的泛型定义没有区分有无初始值的两种场景,导致TypeScript无法准确推断acc的类型。我们可以通过条件类型和重载签名的精准匹配来修复:
步骤1:重构泛型参数,区分返回值类型
将实现的泛型改为根据initialValue是否存在来决定返回值类型,使用条件类型R extends undefined ? T : R来明确类型分支。
步骤2:修正函数实现的类型定义
把函数实现的reduceFunction类型拆分为两种情况:
- 无初始值时,
acc的初始类型是T,每次返回值也是T - 有初始值时,
acc的类型是R,每次返回值也是R
最终修复后的代码
function reduce<T> ( arr: T[], reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T, ): T; function reduce<T> ( arr: T[], reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T, initialValue: T, ): T; function reduce<T, R> ( arr: T[], reduceFunction: (acc: R, item: T, index: number, arr: T[]) => R, initialValue: R ): R; function reduce<T, R = T>( arr: T[], reduceFunction: | ((acc: T, item: T, index: number, arr: T[]) => T) | ((acc: R, item: T, index: number, arr: T[]) => R), initialValue?: R ): R extends undefined ? T : R { if (initialValue === undefined) { // 无初始值场景:acc类型固定为T let prev = arr[0] as T; if (prev === undefined) throw new Error("Reduce of empty array with no initial value"); for (let i = 1; i < arr.length; i++) { prev = (reduceFunction as (acc: T, item: T, index: number, arr: T[]) => T)(prev, arr[i], i, arr); } return prev as R extends undefined ? T : R; } else { // 有初始值场景:acc类型固定为R let prev = initialValue as R; for (let i = 0; i < arr.length; i++) { prev = (reduceFunction as (acc: R, item: T, index: number, arr: T[]) => R)(prev, arr[i], i, arr); } return prev as R extends undefined ? T : R; } } // 使用示例 interface User { name: string; surname: string; } interface UserWithFullname extends User { fullname: string; } const arr: User[] = [{ name: "John", surname: "Doe" }]; const products = [{ capacity: 2, price: 10 }]; const result = reduce<User, UserWithFullname[]>( arr, (acc, item) => { return [...acc, { ...item, fullname: item.name + ' ' + item.surname }] }, [] ) const total = reduce( products, (acc, item) => { return acc + item.capacity * item.price }, 0 ) console.log(result) console.log(total)
关键说明
- 重载签名的精准性:新增了第三个重载(明确
initialValue为必填的R类型),让TypeScript在调用时能更准确地匹配场景。 - 类型断言的合理使用:在函数体内,根据
initialValue的存在情况,将reduceFunction断言为对应的类型分支,避免类型混淆。 - 条件类型返回值:使用
R extends undefined ? T : R让返回值类型根据是否传入初始值自动切换,符合Array.reduce的原生行为。
内容的提问来源于stack exchange,提问作者Ansagan Islamgali
相关产品推荐
相关产品推荐

