导读:本期聚焦于小伙伴创作的《如何用TypeScript实现类型安全的动态分组求和?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何用TypeScript实现类型安全的动态分组求和?》有用,将其分享出去将是对创作者最好的鼓励。

使用 TypeScript 实现类型安全的动态分组求和

在前端数据处理场景中,我们经常需要对数组数据按照指定字段进行分组,再对分组后的数值字段求和。如果使用原生 JavaScript 实现,很容易因为字段类型不匹配、字段不存在等问题出现运行时错误。TypeScript 的类型系统可以帮助我们在编译阶段就规避这些问题,实现类型安全的动态分组求和逻辑。

需求分析

我们希望实现的函数需要满足以下要求:

  • 接收一个对象数组作为数据源,数组元素类型统一
  • 支持动态指定分组依据的字段,该字段必须是对象中存在的属性
  • 支持动态指定求和的字段,该字段必须是对象中存在的数值类型属性
  • 返回分组后的结果,每个分组包含分组键和对应的求和值

类型定义与实现

首先我们需要定义通用的类型,约束函数的入参和返回值,确保类型安全:

// 定义基础对象类型,键为字符串,值为任意类型
type BaseObj = Record<string, any>;

// 提取对象中值为数值类型的键
type NumericKeys<T extends BaseObj> = {
  [K in keyof T]: T[K] extends number ? K : never;
}[keyof T];

// 提取对象中所有键
type AllKeys<T extends BaseObj> = keyof T;

// 分组求和结果类型
interface GroupSumResult<T extends BaseObj, GroupKey extends AllKeys<T>, SumKey extends NumericKeys<T>> {
  groupKey: T[GroupKey];
  sum: number;
}

// 分组求和函数类型定义
function dynamicGroupSum<
  T extends BaseObj,
  GroupKey extends AllKeys<T>,
  SumKey extends NumericKeys<T>
>(
  data: T[],
  groupField: GroupKey,
  sumField: SumKey
): GroupSumResult<T, GroupKey, SumKey>[] {
  // 使用 Map 存储分组中间结果,键为分组值,值为累加和
  const groupMap = new Map<T[GroupKey], number>();

  for (const item of data) {
    const groupValue = item[groupField];
    const sumValue = item[sumField];

    // 这里 TypeScript 已经通过类型约束确保 sumValue 是 number 类型
    const currentSum = groupMap.get(groupValue) || 0;
    groupMap.set(groupValue, currentSum + sumValue);
  }

  // 将 Map 转换为结果数组
  const result: GroupSumResult<T, GroupKey, SumKey>[] = [];
  for (const [groupKey, sum] of groupMap.entries()) {
    result.push({
      groupKey,
      sum
    });
  }

  return result;
}

上面的代码中,我们通过泛型约束实现了类型安全:NumericKeys 工具类型会自动筛选出对象中值为 number 类型的键,避免我们传入非数值字段作为求和字段;AllKeys 类型确保分组字段必须是对象中存在的属性,如果传入不存在的字段,TypeScript 会在编译阶段直接报错。

使用示例

我们定义一个销售数据的类型,然后调用上面的函数进行测试:

// 定义销售数据类型
interface SaleRecord {
  id: number;
  product: string;
  category: string;
  amount: number;
  count: number;
}

// 模拟销售数据
const saleList: SaleRecord[] = [
  { id: 1, product: '手机', category: '电子产品', amount: 3000, count: 1 },
  { id: 2, product: '电脑', category: '电子产品', amount: 8000, count: 1 },
  { id: 3, product: '衬衫', category: '服装', amount: 200, count: 5 },
  { id: 4, product: '裤子', category: '服装', amount: 300, count: 3 },
  { id: 5, product: '平板', category: '电子产品', amount: 4000, count: 2 },
];

// 按 category 分组,对 amount 求和
const categoryAmountSum = dynamicGroupSum(saleList, 'category', 'amount');
console.log('按品类分组销售额求和:', categoryAmountSum);
// 输出:[
//   { groupKey: '电子产品', sum: 15000 },
//   { groupKey: '服装', sum: 500 }
// ]

// 按 category 分组,对 count 求和
const categoryCountSum = dynamicGroupSum(saleList, 'category', 'count');
console.log('按品类分组销量求和:', categoryCountSum);
// 输出:[
//   { groupKey: '电子产品', sum: 4 },
//   { groupKey: '服装', sum: 8 }
// ]

如果尝试传入不存在的字段,比如把分组字段写成 'type',或者把求和字段写成 'product'(字符串类型),TypeScript 会直接提示类型错误,避免运行时出现问题:

// 以下代码会在编译阶段报错
// 错误:'type' 不在 SaleRecord 的键中
// dynamicGroupSum(saleList, 'type', 'amount');

// 错误:'product' 不是数值类型的键
// dynamicGroupSum(saleList, 'category', 'product');

扩展到多字段求和

如果需要同时对多个字段求和,我们可以调整函数支持传入求和字段数组,返回的结果中包含所有求和字段的结果:

// 多字段求和结果类型
interface MultiGroupSumResult<
  T extends BaseObj,
  GroupKey extends AllKeys<T>,
  SumKeys extends NumericKeys<T>[]
> {
  groupKey: T[GroupKey];
  sums: {
    [K in SumKeys[number]]: number;
  };
}

// 多字段分组求和函数
function dynamicMultiGroupSum<
  T extends BaseObj,
  GroupKey extends AllKeys<T>,
  SumKeys extends NumericKeys<T>[]
>(
  data: T[],
  groupField: GroupKey,
  sumFields: [...SumKeys]
): MultiGroupSumResult<T, GroupKey, SumKeys>[] {
  const groupMap = new Map<T[GroupKey], Record<string, number>>();

  for (const item of data) {
    const groupValue = item[groupField];
    let currentSums = groupMap.get(groupValue);

    if (!currentSums) {
      // 初始化所有求和字段为 0
      currentSums = {} as Record<string, number>;
      for (const field of sumFields) {
        currentSums[field as string] = 0;
      }
      groupMap.set(groupValue, currentSums);
    }

    // 累加每个求和字段的值
    for (const field of sumFields) {
      currentSums[field as string] += item[field];
    }
  }

  const result: MultiGroupSumResult<T, GroupKey, SumKeys>[] = [];
  for (const [groupKey, sums] of groupMap.entries()) {
    result.push({
      groupKey,
      sums: sums as MultiGroupSumResult<T, GroupKey, SumKeys>['sums']
    });
  }

  return result;
}

// 多字段求和使用示例
const multiSumResult = dynamicMultiGroupSum(saleList, 'category', ['amount', 'count']);
console.log('多字段分组求和结果:', multiSumResult);
// 输出:[
//   { groupKey: '电子产品', sums: { amount: 15000, count: 4 } },
//   { groupKey: '服装', sums: { amount: 500, count: 8 } }
// ]

通过这种方式,我们可以在 TypeScript 项目中实现灵活、类型安全的动态分组求和逻辑,减少运行时错误,提升代码的健壮性。

TypeScript动态分组求和类型安全泛型约束前端数据处理 本作品最后修改时间:2026-05-22 16:21:57

免责声明:网站部分内容来源于网络或由用户自行发表,内容观点不代表本站立场。本站是个人网站免费分享,内容仅供个人学习、研究或参考使用,如内容中引用了第三方作品,其版权归原作者所有。若内容触犯了您的权益,请联系我们进行处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。前端、网络、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握网站开发与运维所需的核心技术栈。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端逻辑,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。