使用webworker 导出excel

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

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

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

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,同时浏览器的也会卡死这么长时间。随着行列数量的增加,这个时间会随着线性增加。

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

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-style 和 xlsx-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。 

相关文档链接

猜你喜欢

转载自blog.csdn.net/qq_44376306/article/details/131271612