使用 TypeScript 实现类型安全的通用分组求和函数
在前端业务开发中,我们经常会遇到需要对数组数据按某个字段分组,再对分组内的数值字段求和的场景。比如按商品类别统计总销量、按用户部门统计薪资总和等。如果使用普通的 JavaScript 实现,很容易因为字段类型错误、参数传参不规范导致运行时问题。本文将介绍如何使用 TypeScript 实现一个类型安全的通用分组求和函数,在编译阶段就规避大部分类型错误。
需求分析
我们需要实现的函数需要满足以下要求:
- 支持任意类型的数组数据作为输入
- 支持指定任意字段作为分组依据,且该字段必须是数组元素的属性
- 支持指定任意数值字段作为求和依据,且该字段必须是数组元素的数值类型属性
- 分组结果以对象形式返回,键为分组字段的值,值为对应分组内数值字段的总和
- 全程类型提示,避免传入不存在的字段或非数值字段导致错误
核心实现思路
TypeScript 的泛型和 keyof 操作符是实现类型安全的核心。我们可以通过泛型约束输入数组的元素类型,使用 keyof 限制分组字段和求和字段必须是元素自身的属性,再通过条件类型判断求和字段的类型是否为 number,从而在编译阶段就校验参数合法性。
完整代码实现
下面是完整的 TypeScript 实现代码,包含详细的类型注解和逻辑注释:
// 定义一个工具类型,用于筛选对象中值为 number 类型的键
type NumberKeys<T> = {
[K in keyof T]: T[K] extends number ? K : never;
}[keyof T];
/**
* 通用分组求和函数
* @template T 数组元素的类型
* @param list 待处理的数组数据
* @param groupKey 分组依据的字段,必须是 T 类型的属性
* @param sumKey 求和依据的字段,必须是 T 类型中值为 number 的属性
* @returns 分组求和结果,键为分组字段的值,值为求和结果
*/
function groupSum<T extends Record<string, any>>(
list: T[],
groupKey: keyof T,
sumKey: NumberKeys<T>
): Record<string, number> {
// 初始化结果对象
const result: Record<string, number> = {};
for (const item of list) {
// 获取分组字段的值,作为结果的键
const groupValue = String(item[groupKey]);
// 获取当前元素的求和字段值
const sumValue = item[sumKey];
// 如果结果中还没有该分组,初始化为0
if (!result.hasOwnProperty(groupValue)) {
result[groupValue] = 0;
}
// 累加求和值
result[groupValue] += sumValue;
}
return result;
}代码解析
首先我们看工具类型 NumberKeys<T>,它通过映射类型遍历 T 的所有属性,只有属性值为 number 类型的键才会被保留,最终得到 T 中所有数值类型属性的联合类型。这样在传入 sumKey 参数时,TypeScript 会自动提示只能选择数值类型的字段,避免传入字符串等其他类型的字段。
函数本身使用泛型 T 约束数组元素的类型,groupKey 被限制为 T 的任意属性键,sumKey 则被限制为 T 中数值类型的属性键。在函数内部,我们遍历输入数组,取出每个元素的分组字段值作为结果键,累加对应数值字段的值,最终返回分组求和的结果。
使用示例
下面通过几个实际场景的示例,展示这个函数的使用方法:
示例1:商品销量分组统计
// 定义商品数据类型
interface GoodsItem {
id: number;
name: string;
category: string;
sales: number;
price: number;
}
// 模拟商品数据
const goodsList: GoodsItem[] = [
{ id: 1, name: '手机', category: '数码', sales: 100, price: 2999 },
{ id: 2, name: '电脑', category: '数码', sales: 50, price: 6999 },
{ id: 3, name: '衬衫', category: '服装', sales: 200, price: 199 },
{ id: 4, name: '裤子', category: '服装', sales: 150, price: 299 },
];
// 按 category 分组,统计 sales 总和
const categorySalesSum = groupSum(goodsList, 'category', 'sales');
console.log(categorySalesSum);
// 输出: { 数码: 150, 服装: 350 }
// 按 category 分组,统计 price 总和
const categoryPriceSum = groupSum(goodsList, 'category', 'price');
console.log(categoryPriceSum);
// 输出: { 数码: 9998, 服装: 498 }示例2:错误传参的类型提示
如果传入不符合要求的参数,TypeScript 会在编译阶段直接报错:
// 错误1:sumKey 传入非数值类型的字段
// 报错:类型“"name"”的参数不能赋给类型“NumberKeys<GoodsItem>”的参数
groupSum(goodsList, 'category', 'name');
// 错误2:groupKey 传入不存在的字段
// 报错:类型“"invalidKey"”的参数不能赋给类型“keyof GoodsItem”的参数
groupSum(goodsList, 'invalidKey', 'sales');
// 错误3:list 元素类型中不存在对应字段
const wrongList = [{ a: 1 }, { a: 2 }];
// 报错:类型“"b"”的参数不能赋给类型“keyof { a: number }”的参数
groupSum(wrongList, 'b', 'a');总结
通过 TypeScript 的泛型和类型工具,我们实现了一个完全类型安全的通用分组求和函数。这个函数不仅可以在编译阶段规避大部分参数传参错误,还保留了很好的通用性,能够适配各种不同的数据结构。在实际业务中,类似的思路还可以扩展到分组求平均、分组求最大值等场景,只需要调整对应的聚合逻辑即可,类型约束部分可以复用相同的模式。
TypeScript分组求和泛型编程类型安全前端数据处理 本作品最后修改时间:2026-05-22 16:22:59