使用 Web Worker 导出 Excel

前言

在项目开发过程中,导出 Excel 文件是很常见的功能。根据生成 Excel 文件的方式,通常分为后端生成和前端生成。

前端实现下载后端生成的 Excel 通常有两种方式,①是后端返回 Excel 文件的临时下载地址前端直接下载,②是后端返回 ArrayBuffer 二进制数据,前端处理后下载。

而前端生成 Excel 就简单的多了,后端仅需返回指定格式的 JSON 数据就可以了,生成 Excel 这一步由前端浏览器完成,这样就可以大大减轻服务器压力,节约服务器资源。但是前端在主线程内对大量数据进行excel导出时不可避免的会对主线程进行阻塞,造成页面卡顿,影响用户体验

Web Worker ?

可以先查看另一篇文章 Web Worker API

SheetJS

SheetJS,又叫 XLSXJS,官网称为 SheetJS。它支持浏览器、nodejs、deno、和 react-native,浏览器兼容 ie10+。

SheetJS 社区版提供经过实战考验的开源解决方案,用于从几乎所有复杂的电子表格中提取有用的数据,并生成新的电子表格,这些新的电子表格可以与传统软件和现代软件一起使用。
下面是一个导出 十万行 20列 数据的一个示例:

const cloLen = 20;
const rowLen = 100000;
const row = Array(cloLen).fill().reduce((t, e, i) => ({ ...t, [`字段${i + 1}`]: `字段${i + 1}的值` }), {});
const list = Array(rowLen).fill({ ...row });

const wb = XLSX.utils.book_new();
const ws = XLSX.utils.json_to_sheet(list, { dense: true });
XLSX.utils.book_append_sheet(wb, ws);
XLSX.writeFile(wb, `${new Date().toLocaleTimeString()}.xlsx`, {
  bookSST: true,
});

因为 JavaScript 的单线程特性,大量的 JS 运算会导致浏览器渲染进程阻塞,出现不同程度的浏览器假死现象。

以我的 Mac Pro为例,生成一个十万行 20列的 Excel 约执行 1700 ~ 1800ms,同时浏览器的也会卡死这么长时间。随着行列数量的增加,这个时间会随着线性增加。

这个时候就需要使用 Web Worker来避免浏览器渲染阻塞了。

const cloLen = 20;
const rowLen = 100000;
const row = Array(cloLen).fill().reduce((t, e, i) => ({ ...t, [`字段${i + 1}`]: `字段${i + 1}的值` }), {});
const list = Array(rowLen).fill({ ...row });

const SheetJSWebWorker = new Worker(
  URL.createObjectURL(
    new Blob([
      `
      importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js");
      onmessage = ({ data }) => {
        const wb = XLSX.utils.book_new();
        const ws = XLSX.utils.json_to_sheet(data, { dense: true });
        XLSX.utils.book_append_sheet(wb, ws);
        postMessage(XLSX.write(wb, { type: "array", bookType: "xlsx", bookSST: true, compression: true }))
      };
    `,
    ])
  )
);

SheetJSWebWorker.postMessage(list);
SheetJSWebWorker.onmessage = ({ data }) => {
  const a = document.createElement("a");
  a.download = `${new Date().toLocaleTimeString()}.xlsx`;
  a.href = URL.createObjectURL(
    new Blob([data], { type: "application/octet-stream" })
  );
  a.click();
};

此时生成 Excel 的执行时间大约为 2400 ~ 2500ms。但是页面渲染丝毫没有任何阻塞。

需要注意的是 SheetJS 是一个商业项目。我们使用的是完全免费的社区版。社区版仅提供了生成 Excel、合并单元格等很有限的功能。我们想要自定义生成 Excel 的样式,需要借助一些 SheetJS 的开源周边,比如:xlsx-stylexlsx-populate 等。这里就不展开了,有兴趣大家自行研究。

如果想要导出有自定义功能的 Excel,就需要借助 ExcelJS 实现了。

ExcelJS

ExcelJS 提供了更为强大的自定义导出 Excel 功能,但是性能只有 SheetJS 的四分之一。

单线程示例:
const cloLen = 20;
const rowLen = 100000;
const row = Array(cloLen).fill().reduce((t, e, i) => ({ ...t, [`字段${i + 1}`]: `字段${i + 1}的值` }), {});
const list = Array(rowLen).fill({ ...row });

const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet();
worksheet.columns = Object.keys(list[0]).map((e) => ({ header: e, key: e, width: 20 }));
worksheet.addRows(list);

const a = document.createElement("a");
a.download = `${new Date().toLocaleTimeString()}.xlsx`;
a.href = window.URL.createObjectURL(
  new Blob([await workbook.xlsx.writeBuffer()], {
    type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8",
  })
);
a.click();
多线程示例:
const cloLen = 20;
const rowLen = 100000;
const row = Array(cloLen).fill().reduce((t, e, i) => ({ ...t, [`字段${i + 1}`]: `字段${i + 1}的值` }), {});
const list = Array(rowLen).fill({ ...row });

const ExcelJSWebWorker = new Worker(
  URL.createObjectURL(
    new Blob([
      `
      importScripts("https://cdn.bootcdn.net/ajax/libs/exceljs/4.3.0/exceljs.js");
      onmessage = async ({ data }) => {
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet();
        worksheet.columns = Object.keys(data[0]).map(e => ({ header: e, key: e, width: 20 }));
        worksheet.addRows(data);
        postMessage(await workbook.xlsx.writeBuffer())
      };
    `,
    ])
  )
);

ExcelJSWebWorker.postMessage(list);
ExcelJSWebWorker.onmessage = ({ data }) => {
  const a = document.createElement("a");
  a.download = `${new Date().toLocaleTimeString()}.xlsx`;
  a.href = window.URL.createObjectURL(
    new Blob([data], {
      type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8",
    })
  );
  a.click();
};

CSV

大多数时候,我们导出的 Excel 文件也可以用 CSV 格式生成。

从名字上看一个是导出 CSV,一个是导出 Excel。

那么这二者有什么区别呢?

  • Excel 是一个电子表格,将文件保存为自己的专有格式,即 xls 或 xlsx,它保存有关工作簿中所有工作表的信息。

  • CSV 代表 Comma Separated Values ,这是一个纯文本格式,用逗号分隔一系列值,但不包含格式,公式,宏等。

总结来说**,Excel 不仅可以存储数据,还可以存放对数据的操作结果,CSV 文件只是一个文本文件,它只存储数据,因此十分容易生成**。

同时 CSV 文档使用 word 打开后默认也是以 Excel 的形式展现,只需要简单的处理就可以变为 Excel。

单线程示例:
const cloLen = 20;
const rowLen = 100000;
const row = Array(cloLen).fill().reduce((t, e, i) => ({ ...t, [`字段${i + 1}`]: `字段${i + 1}的值` }), {});
const list = Array(rowLen).fill({ ...row });

let str = Object.keys(list[0]).join() + "\n";
for (let i = 0; i < list.length; i++) {
  for (const key in list[i]) {
    str += `${list[i][key] + "\t"},`;
  }
  str += "\n";
}
const a = document.createElement("a");
a.href = "data:text/csv;charset=utf-8,\ufeff" + encodeURIComponent(str);
a.download = `${new Date().toLocaleTimeString()}.csv`;
a.click();
多线程示例:
const cloLen = 20;
const rowLen = 100000;
const row = Array(cloLen).fill().reduce((t, e, i) => ({ ...t, [`字段${i + 1}`]: `字段${i + 1}的值` }), {});
const list = Array(rowLen).fill({ ...row });

const VanillaJSWebWorker = new Worker(
  URL.createObjectURL(
    new Blob([
      `
      importScripts("https://cdn.bootcdn.net/ajax/libs/exceljs/4.3.0/exceljs.js");
      onmessage = async ({ data }) => {
        let str = Object.keys(data[0]).join() + String.fromCharCode(10)
        for (let i = 0; i < data.length; i++) {
          for (const key in data[i]) {
            str += data[i][key] + '\t,';
          }
          str += String.fromCharCode(10);
        }
        postMessage('data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(str))
      };
    `,
    ])
  )
);

VanillaJSWebWorker.postMessage(list);
VanillaJSWebWorker.onmessage = ({ data }) => {
  const a = document.createElement("a");
  a.download = `${new Date().toLocaleTimeString()}.csv`;
  a.href = data;
  a.click();
};

总结

前端生成 Excel 的最大优点就是可以减少服务器资源的消耗,充分利用了客户端的算力资源,将计算压力由服务器转移到浏览器,其次就是传输数据量更小,相对而言更快。

同时缺点也很明显,首先 JavaScript 并不属于高计算性能的编程语言,相对计算性能比不上服务器计算。其次,生成导出文件的快慢最重要的影响因素取决于用户的硬件,不用性能的电脑可能存在较大的用户体验差异。

下面是以上几个方案的常规适用场景,请参考:

  • 百万级数据使用 CSV。
  • 大量数据无样式功能要求使用 SheetJS。
  • 少量数据功能齐全的使用 ExcelJS。
  • 大量数据需要功能使用 商业版 SheetJS。

相关文档链接

使用Web Worker优化代码
Web Workers | MDN
Web Worker 使用教程 - 阮一峰的网络日志
WebWorker 优化数据导出下载 Excel 用户操作体验

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Web Worker 是一个运行在后台的 JavaScript 脚本,可以在不阻塞主线程的情况下执行复杂的计算任务。它可以用于读取和处理 Excel 文件。 要在 Web Worker 中读取 Excel 文件,你可以使用第三方库如 `xlsx` 或 `exceljs` 来解析和操作 Excel 文件。下面是一个使用 `xlsx` 库的示例代码: 在主线程中,你可以创建和发送一个 Web Worker,然后将 Excel 文件的内容传递给它: ```javascript // 主线程 const worker = new Worker('worker.js'); // 读取 Excel 文件 const fileInput = document.getElementById('fileInput'); fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; const reader = new FileReader(); reader.onload = (e) => { const data = new Uint8Array(e.target.result); worker.postMessage(data); }; reader.readAsArrayBuffer(file); }); ``` 在 Web Worker 脚本中,你可以接收并处理 Excel 文件的内容: ```javascript // worker.js importScripts('https://unpkg.com/xlsx/dist/xlsx.full.min.js'); self.onmessage = (event) => { const data = new Uint8Array(event.data); const workbook = XLSX.read(data, { type: 'array' }); // 处理 Excel 数据 // 示例:读取第一个工作表的数据 const worksheet = workbook.Sheets[workbook.SheetNames[0]]; const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); // 将处理后的数据发送回主线程 self.postMessage(jsonData); }; ``` 这是一个简单的示例,演示了如何使用 Web Worker 读取 Excel 文件。你可以根据具体需求进行修改和扩展。请注意,在实际使用中,你可能需要处理不同的 Excel 格式和数据结构,以及错误处理和其他逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值