从后端数据到前端图表:深入解析 reduce 与 flatMap 的数据整形实战

在前端开发中,我们常常会遇到一个场景:后端返回的数据结构非常"扁平"和"原始",而前端的图表库(如 ECharts)却需要一个结构清晰、高度组织化的数据格式。如何高效、优雅地完成这次数据"整形"(Data Transformation),是衡量前端工程师代码质量的重要标准。

今天,我们就通过一个真实的季度养护记录图表需求,来深入剖析 JavaScript 中两个强大的数组方法:reduceflatMap

需求分析:从"行"到"列"的转变

首先,我们来看看后端返回的数据是什么样的。这是一个典型的数组对象(Array of Objects)格式,每一行代表一个特定任务在某个季度的数量。

复制代码
// 后端返回的原始数据 (rawData)
const rawData = [
    { year: 2026, quarter: 1, taskType: "传感器校验", count: 1 },
    { year: 2026, quarter: 1, taskType: "管道清洗", count: 1 },
    { year: 2026, quarter: 1, taskType: "设备润滑", count: 5 },
    { year: 2026, quarter: 2, taskType: "传感器校验", count: 1 },
    { year: 2026, quarter: 2, taskType: "管道清洗", count: 1 },
    { year: 2026, quarter: 2, taskType: "设备润滑", count: 2 }
];

然而,ECharts 的多条折线图需要的是另一种格式:x 轴代表时间,而每一种任务类型(taskType)都成为对象的一个属性(即一"列")。

复制代码
// ECharts 需要的目标数据格式
const targetData = [
  { time: '2026-Q1', "管道清洗": 1, "设备润滑": 5, "传感器校验": 1 },
  { time: '2026-Q2', "管道清洗": 1, "设备润滑": 2, "传感器校验": 1 }
];

我们的任务就是编写一个函数,将 rawData 转换为 targetData。这个过程可以分为两步:

  1. 分组与聚合 :将原始数据按照 year-quarter 进行分组,并将不同类型的任务数量聚合到同一个对象中。
  2. 提取图例:从聚合后的数据中,提取出所有的任务类型,用于配置图表的图例(legend)和系列(series)。
️ 第一步:使用 reduce 进行数据分组与聚合

reduce 方法是 JavaScript 中最强大的数组方法之一,它可以将数组"缩减"为任何一个你想要的值------一个数字、一个字符串,或者像我们这里需要的,一个对象。

它的核心思想是:遍历数组的每一项,并通过一个"累加器"(accumulator)来累积结果。

复制代码
const transformQuarterData = (rawData) => {
  // 1. 按 year+quarter 分组
  const grouped = rawData.reduce((acc, item) => {
    // 生成时间键,如 '2026-Q1'
    const time = `${item.year}-Q${item.quarter}`;
    
    // 如果累加器中还没有这个时间键,就初始化它
    // 注意这里我们创建了一个以 time 为属性的对象 { time: '2026-Q1' }
    if (!acc[time]) {
      acc[time] = { time };
    }
    
    // 将当前项的任务类型和数量添加到对应的对象中
    // 例如:acc['2026-Q1']['管道清洗'] = 1
    acc[time][item.taskType] = item.count;
    
    // 返回累加器,供下一次迭代使用
    return acc;
  }, {}); // 第二个参数 '{}' 是累加器的初始值,一个空对象

  // 2. 将聚合后的对象转为数组
  // Object.values() 会取出对象的所有值,组成一个新数组
  return Object.values(grouped);
};

reduce 执行过程详解:

迭代次数 item (当前项) acc (累加器,迭代后)
初始值 - {}
1 {..., taskType: "传感器校验", ...} { '2026-Q1': { time: '2026-Q1', "传感器校验": 1 } }
2 {..., taskType: "管道清洗", ...} { '2026-Q1': { time: '2026-Q1', "传感器校验": 1, "管道清洗": 1 } }
3 {..., taskType: "设备润滑", ...} { '2026-Q1': { time: '2026-Q1', "传感器校验": 1, "管道清洗": 1, "设备润滑": 5 } }
4 {..., taskType: "传感器校验", ...} { '2026-Q1': {...}, '2026-Q2': { time: '2026-Q2', "传感器校验": 1 } }
... ... ...

最终,grouped 对象的结构如下:

复制代码
{
  '2026-Q1': { time: '2026-Q1', "传感器校验": 1, "管道清洗": 1, "设备润滑": 5 },
  '2026-Q2': { time: '2026-Q2', "传感器校验": 1, "管道清洗": 1, "设备润滑": 2 }
}

通过 Object.values(grouped),我们就得到了 ECharts 需要的数组格式。

第二步:使用 flatMap 动态提取图例

数据准备好了,但图表的图例(legend)和系列(series)不能写死。我们需要从处理好的 data 中动态地提取出所有的任务类型(taskType),也就是每个数据对象中除了 time 以外的所有键。

这时,flatMap 就派上用场了。你可以把它看作是 map + flat 的组合。

  1. map: 遍历数组中的每个对象,提取出它的所有键(Object.keys(item))。

  2. filter: 过滤掉 'time' 这个键。

  3. flat: 将 map 产生的多个小数组"拍平"成一个一维数组。

  4. Set: 使用 Set 数据结构去除重复的任务类型。

    // 假设 data 是 transformQuarterData 处理后的结果
    const data = [
    { time: '2026-Q1', "管道清洗": 1, "设备润滑": 5, "传感器校验": 1 },
    { time: '2026-Q2', "管道清洗": 1, "设备润滑": 2, "传感器校验": 1 }
    ];

    const taskTypes = [...new Set(data.flatMap(item =>
    Object.keys(item).filter(key => key !== 'time')
    ))];

    console.log(taskTypes);
    // 输出: ['管道清洗', '设备润滑', '传感器校验']

flatMap 执行过程详解:

  1. data.map(...) 会产生一个二维数组:

    复制代码
    [
      ['管道清洗', '设备润滑', '传感器校验'], // 来自第一个对象
      ['管道清洗', '设备润滑', '传感器校验']  // 来自第二个对象
    ]
  2. .flat() 会将这个二维数组拍平成一维数组:

    复制代码
    ['管道清洗', '设备润滑', '传感器校验', '管道清洗', '设备润滑', '传感器校验']
  3. new Set(...) 会去除重复项:

    复制代码
    Set(3) {'管道清洗', '设备润滑', '传感器校验'}
  4. 最后用扩展运算符 [...Set] 将其转回数组。

有了 taskTypes 数组,我们就可以轻松地用它来生成 ECharts 的 legend.dataseries 配置了。

复制代码
series: taskTypes.map(type => ({
  name: type,
  type: 'line',
  data: data.map(item => item[type] || 0) // 动态获取每个任务类型的数据
}))
总结

通过这次实战,我们清晰地看到了 reduceflatMap 在数据整形中的强大威力:

  • reduce :是数据聚合和重组的利器。当你需要将一个复杂的数组转换成一个对象、Map,或者另一个结构完全不同的数组时,reduce 是你的首选。它让你在一次遍历中完成所有逻辑,代码既高效又优雅。
  • flatMap :是处理嵌套数组结构的便捷工具。当你需要先对数组元素进行映射(map),而映射的结果又是一个数组,并且你希望最终得到一个扁平化的一维数组时,flatMapmap().flat() 更加简洁和语义化。

掌握这两个方法,能让你在处理复杂数据时游刃有余,写出更具现代 JavaScript 风格的代码。

相关推荐
你很易烊千玺13 小时前
JS 数组所有变态遍历・完整案例 + 场景 + 对比
javascript·数组
lifejump14 小时前
Dede(织梦)CMS渗透测试(all)
前端·网络·安全·web安全
扬帆破浪14 小时前
sidecar崩溃后前端怎么续命 重启策略与状态保留
前端·人工智能·架构·开源·知识图谱
光影少年14 小时前
前端算法题
前端·javascript·算法
Lee川14 小时前
从输入框到智能匹配:一文读懂搜索功能的完整实现
前端·后端
朝阳3915 小时前
React【面试】
前端·react.js·面试
漓漾li15 小时前
每日面试题(2026-05-15)- 前端
前端·vue.js·react.js
进击切图仔15 小时前
RAG 加载 pdf 文档
linux·前端·pdf
小小小小宇15 小时前
git 大仓库拉取卡顿问题
前端
前端那点事15 小时前
告别低级冗余!10个前端原生高阶技巧,让代码更优雅、性能更出众
前端·vue.js