使用JS reduce方法转换后台接口数据格式
在前端开发中,我们经常会遇到后台接口返回的数据格式不符合前端组件要求的情况,这时就需要对数据进行格式转换。JavaScript数组的reduce方法是一个非常灵活的工具,能够高效地将原始数据转换为目标格式,尤其是处理结构复杂的接口数据时,优势更加明显。
一、reduce方法基础回顾
reduce是数组原型上的方法,它会遍历数组的每个元素,通过回调函数将上一个处理结果和当前元素进行计算,最终返回一个累加后的结果。其基础语法如下:
array.reduce(function(prev, cur, index, array) {
// 处理逻辑
return 新的累加值;
}, initialValue);参数说明:
prev:上一次回调函数返回的值,或者是初始值
initialValuecur:当前正在处理的元素
index:当前元素的索引,可选
array:调用
reduce的原数组,可选initialValue:初始累加值,可选,如果不传则默认以数组第一个元素作为初始值,从第二个元素开始遍历
二、常见接口数据转换场景示例
场景1:将扁平接口数据转换为树形结构
很多后台接口返回的是扁平的列表数据,而前端树形组件(如级联选择器、树形表格)需要嵌套的树形结构,这时可以用reduce完成转换。假设后台返回的用户部门数据如下:
// 后台接口返回的原始扁平数据
const apiData = [
{ id: 1, name: '技术部', parentId: 0 },
{ id: 2, name: '前端组', parentId: 1 },
{ id: 3, name: '后端组', parentId: 1 },
{ id: 4, name: '产品部', parentId: 0 },
{ id: 5, name: '产品设计组', parentId: 4 },
{ id: 6, name: '需求分析组', parentId: 4 }
];我们需要将其转换为以parentId为关联关系的树形结构,实现代码如下:
function transformToTree(flatData) {
// 第一步:用reduce生成id到数据项的映射,方便后续查找父节点
const map = flatData.reduce((prev, cur) => {
prev[cur.id] = { ...cur, children: [] };
return prev;
}, {});
// 第二步:用reduce遍历映射,将子节点挂载到父节点的children属性上
const treeData = flatData.reduce((prev, cur) => {
const currentItem = map[cur.id];
if (cur.parentId === 0) {
// 根节点直接加入结果数组
prev.push(currentItem);
} else {
// 非根节点找到父节点,挂载到父节点的children中
if (map[cur.parentId]) {
map[cur.parentId].children.push(currentItem);
}
}
return prev;
}, []);
return treeData;
}
// 调用转换方法
const resultTree = transformToTree(apiData);
console.log(resultTree);转换后的树形结构如下,符合前端树形组件的数据要求:
[
{
id: 1,
name: '技术部',
parentId: 0,
children: [
{ id: 2, name: '前端组', parentId: 1, children: [] },
{ id: 3, name: '后端组', parentId: 1, children: [] }
]
},
{
id: 4,
name: '产品部',
parentId: 0,
children: [
{ id: 5, name: '产品设计组', parentId: 4, children: [] },
{ id: 6, name: '需求分析组', parentId: 4, children: [] }
]
}
]场景2:接口数据按指定字段分组聚合
有时需要按某个字段将接口数据进行分组统计,比如按订单状态分组统计订单数量,后台返回的原始订单数据如下:
// 后台返回的原始订单数据
const orderList = [
{ orderId: 'O001', status: '待支付', amount: 199 },
{ orderId: 'O002', status: '已支付', amount: 299 },
{ orderId: 'O003', status: '待支付', amount: 159 },
{ orderId: 'O004', status: '已发货', amount: 399 },
{ orderId: 'O005', status: '已支付', amount: 89 }
];使用reduce按status字段分组,同时统计每个状态的总订单金额和订单数量:
const groupedOrder = orderList.reduce((prev, cur) => {
const status = cur.status;
if (!prev[status]) {
// 如果当前状态的分组不存在,初始化分组数据
prev[status] = {
status: status,
count: 0,
totalAmount: 0
};
}
// 累加当前状态的订单数量和金额
prev[status].count += 1;
prev[status].totalAmount += cur.amount;
return prev;
}, {});
// 如果需要将对象形式转换为数组,可再调用Object.values
const groupedOrderArray = Object.values(groupedOrder);
console.log(groupedOrderArray);转换后的结果如下,方便前端直接渲染统计表格:
[
{ status: '待支付', count: 2, totalAmount: 358 },
{ status: '已支付', count: 2, totalAmount: 388 },
{ status: '已发货', count: 1, totalAmount: 399 }
]场景3:转换接口数据的字段名和格式
后台接口返回的字段名可能不符合前端组件的命名规范,或者需要对字段值做格式化处理,比如将后台返回的user_id、user_name转换为前端需要的value、label格式,同时格式化日期字段:
// 后台返回的用户列表数据
const userApiData = [
{ user_id: 1, user_name: '张三', create_time: '2024-01-15T08:30:00.000Z' },
{ user_id: 2, user_name: '李四', create_time: '2024-01-16T10:15:00.000Z' },
{ user_id: 3, user_name: '王五', create_time: '2024-01-17T14:20:00.000Z' }
];
// 转换函数:重命名字段,格式化时间
const formatUserData = (apiData) => {
return apiData.reduce((prev, cur) => {
const formatTime = new Date(cur.create_time).toLocaleDateString('zh-CN');
prev.push({
value: cur.user_id,
label: cur.user_name,
createTime: formatTime
});
return prev;
}, []);
};
const formattedUsers = formatUserData(userApiData);
console.log(formattedUsers);转换后的数据可以直接用于下拉选择框等组件:
[
{ value: 1, label: '张三', createTime: '2024/1/15' },
{ value: 2, label: '李四', createTime: '2024/1/16' },
{ value: 3, label: '王五', createTime: '2024/1/17' }
]三、使用reduce转换数据的注意事项
1. 初始值initialValue的设置非常重要,如果目标结果是数组就设置初始值为[],目标是对象就设置为{},避免默认初始值带来的类型错误。
2. 处理复杂结构时,可以先拆分步骤,比如先生成映射表再处理关联关系,避免单个reduce逻辑过于复杂,提升代码可读性。
3. 如果接口数据量非常大,需要注意reduce遍历的性能,避免在回调函数中做过多的嵌套查询或复杂计算,必要时可以先预处理数据再调用reduce。
4. 转换过程中尽量不要修改原始接口数据,建议通过扩展运算符...复制对象后再处理,避免影响其他依赖原始数据的逻辑。
提示:如果接口数据是从https://www.ipipp.com这类第三方服务获取,也可以在获取数据后第一时间用
reduce完成格式转换,保证后续业务逻辑使用统一的数据格式。