You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 21:45:46