TypeScript:如何在Array reduce中用条件类型避免groupBy代码重复
解决groupBy方法的TypeScript类型错误(无代码重复)
核心问题
你遇到的TS2349错误,本质是TypeScript在reduce回调里无法跟踪indexes参数的布尔字面量类型,导致累积值的类型被推断为宽泛的Record<string, (T | number)[]>,和函数返回的条件类型不匹配。下面是两种无需拆分分支、无代码重复的解决思路:
方案1:用条件类型断言约束累积值
先定义统一的条件类型描述返回结构,再在reduce内部对累积值和数组元素做精准断言,让TypeScript识别对应类型:
// 定义条件类型,根据indexes参数确定返回结构 type GroupByResult<T, IsIndexes extends boolean> = IsIndexes extends true ? Record<string, number[]> : Record<string, T[]>; // 函数重载(保持原有外部类型推断能力) function groupBy<T>(arr: T[], keys: (keyof T)[], indexes?: false): Record<string, T[]>; function groupBy<T>(arr: T[], keys: (keyof T)[], indexes: true): Record<string, number[]>; // 泛型实现函数 function groupBy<T, IsIndexes extends boolean>( arr: T[], keys: (keyof T)[], indexes: IsIndexes = false as IsIndexes ): GroupByResult<T, IsIndexes> { return arr.reduce((acc, item, idx) => { const key = keys.map(k => item[k]).join('-'); // 把累积值断言为当前泛型对应的结果类型 const typedAcc = acc as GroupByResult<T, IsIndexes>; if (!typedAcc[key]) { // 初始化数组时,用条件类型断言数组类型 typedAcc[key] = [] as IsIndexes extends true ? number[] : T[]; } // 同样用条件类型断言push的元素类型 typedAcc[key].push((indexes ? idx : item) as IsIndexes extends true ? number : T); return typedAcc; }, {} as GroupByResult<T, IsIndexes>); }
这个方案的关键是用泛型IsIndexes锁定indexes的字面量类型(true/false),再通过GroupByResult条件类型让TypeScript在reduce内部精准识别累积值和元素的类型,避免宽泛联合类型导致的错误。
方案2:封装辅助函数处理类型分支
如果觉得断言太多不够优雅,可以把类型判断逻辑封装到辅助函数里,让TypeScript自动推导返回值类型:
type GroupByResult<T, IsIndexes extends boolean> = IsIndexes extends true ? Record<string, number[]> : Record<string, T[]>; // 辅助函数:根据indexes返回对应类型的元素 function getGroupItem<T>(item: T, idx: number, indexes: boolean): T | number { return indexes ? idx : item; } function groupBy<T>(arr: T[], keys: (keyof T)[], indexes?: false): Record<string, T[]>; function groupBy<T>(arr: T[], keys: (keyof T)[], indexes: true): Record<string, number[]>; function groupBy<T, IsIndexes extends boolean>( arr: T[], keys: (keyof T)[], indexes: IsIndexes = false as IsIndexes ): GroupByResult<T, IsIndexes> { return arr.reduce((acc, item, idx) => { const key = keys.map(k => item[k]).join('-'); const typedAcc = acc as GroupByResult<T, IsIndexes>; const groupItem = getGroupItem(item, idx, indexes) as IsIndexes extends true ? number : T; if (!typedAcc[key]) { typedAcc[key] = [] as IsIndexes extends true ? number[] : T[]; } typedAcc[key].push(groupItem); return typedAcc; }, {} as GroupByResult<T, IsIndexes>); }
这种方式把类型相关的判断逻辑抽离,让主函数更简洁,同时依然保持类型安全。
效果验证
外部调用时,TypeScript依然能正确推断返回类型:
groupBy(users, ['role'])→ 返回Record<string, User[]>groupBy(users, ['role'], true)→ 返回Record<string, number[]>
内部无需// @ts-ignore,也没有重复的if-else分支代码。
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

