在Web项目开发过程中,我们经常会遇到大数据量展示的需求。如果一次性把所有数据加载到页面上,势必会造成页面卡顿、反应慢等性能问题。那么对于这种场景,我们如何该做性能优化呢?

一、问题背景

前端性能优化一直是一个比较大的话题。我们在实际项目中可能会遇到很多关于性能的问题,常见的优化手法也很多,比如合并JS/CSS文件、减少http请求、懒加载、图片压缩等。如果我们只聚焦于大数据量展示的场景,那么如何将后端丢来的数以万计的数据高效展示出来呢?

答案很简单,核心思想就是分段去展示数据!每次只加载可视区域(也就是一页)的报表数据,当翻到下一页时再取下一页的数据进行展示。这样每次报表渲染的时候,浏览器只会渲染当前页的内容,不会一次性把几万行报表数据渲染加载到页面上,这种做法也成称为“分页加载”。

二、优化实战

作为一个报表控件的技术支持人员,这里我就以技术社区中最常被问到的纯前端报表项目为例,展示一下如何利用分页加载,快速展示大数据量报表。例子非常简单,包学包会。

1、自定义报表翻页按钮

要想分页加载报表数据,首先要自定义翻页按钮,当点用户击到下一页时,才去加载下一页的数据。ActiveReports JS纯前端报表控件提供了自定义按钮的功能,我们需要做的是利用这个API,为报表页面添加翻页按钮。第一步,我们需要创建自定义按钮,响应用户的翻页操作:

const prevPageButton = {
      key: '$prevPageButton',
      text: "上一页",
      title: "prevPageButton",
      enabled: true,
      action: function () {}
};
const nextPageButton = {
      key: '$nextPageButton',
      text: "下一页",
      title: "nextPageButton",
      enabled: true,
      action: function () {}
};
const pageCount = {
      key: '$pageCount',
      text: `1/ ${totalPage}`,
      title: "pageCount",
      enabled: true
};

这里我定义了三个按钮,分别为“上一页”、“当前页”、“下一页”,其中“当前页”在翻页过程中会不断计算更新页面展示。然后,我们将这几个按钮添加到ActiveReports JS的viewer对象上:

viewer = this.$refs.reportViewer.Viewer();
this.designerHidden = true;
[ prevPageButton, pageCount, nextPageButton ].forEach((item) => {
      viewer.toolbar.addItem(item);
})
viewer.toolbar.updateLayout({default: ["$zoom", "$split", "$print","$prevPageButton", "$pageCount", "$nextPageButton"]});

这里用到的API中各参数的含义不再赘述,可以参考帮助文档:https://demo.grapecity.com.cn/activereportsjs/api/

2、分页加载数据

翻页按钮添加完之后,接着我们需要实现分页取数据,只展示当前页数据的逻辑。首先,我们需要提前计算好每页能够展示多少条数据,比如我这里每页展示29条数据,所以每次从后端数据中取出29条数据来传给报表展示:

let currentPage  = 1;
//每页展示的数据条数
let once = 29;
//计算需要展示的总页数
const totalPage = salesData.length % once ? Math.floor(salesData.length / once) + 1 : Math.floor(salesData.length / once);

初始预览时,传给报表前29条数据:

openReportView(salesData.slice(0, 29));

function openReportView(data) {
      definition.DataSources[0].ConnectionProperties.ConnectString = "jsondata=" + JSON.stringify(data);
      viewer.open(definition);
}

在翻页时加入判断逻辑,将数据分段传给报表对象。这里的逻辑需要定义在之前创建的button所提供的action接口中。在写好翻页的数据处理逻辑之后,我们还需要更新当前页的页号,代码如下:

const prevPageButton = {
      key: '$prevPageButton',
      text: "上一页",
      title: "prevPageButton",
      enabled: true,
      action: function () {
        if(currentPage === 1) {
          return null;
        } else {
          currentPage --;
          let data = salesData.slice((currentPage-1) * once, currentPage * once);
          openReportView(data);
          pageCount.onUpdate([], pageCount);
        }
      }
    };
    const nextPageButton = {
      key: '$nextPageButton',
      text: "下一页",
      title: "nextPageButton",
      enabled: true,
      //action接口中实现更新每次展示的数据逻辑
      action: function () {
        if(currentPage === totalPage) {
          return null;
        } else  {
          currentPage ++;
          let data = currentPage === totalPage ? salesData.slice((currentPage-1) * once, salesData.length) : salesData.slice((currentPage-1) * once, currentPage * once);
          openReportView(data);
          //调用更新展示当前页号的逻辑
          pageCount.onUpdate([], pageCount);
        }
      }
    };
    const pageCount = {
      key: '$pageCount',
      text: `1/ ${totalPage}`,
      title: "pageCount",
      enabled: true,
      //实现更新页号的逻辑
      onUpdate: function (args, currentItem) {
        currentItem.text = `${currentPage}/${totalPage}`;
      }
    };

三、效果对比

至此我们利用分页加载机制,展示大数据量报表的逻辑就已经实现完成了。让我们来对比看看效果吧!

未分页时:

分页加载数据:

以上两个GIF帧率相同,但对比非常明显,未分页前点击预览之后有两秒左右的空白加载时间,而分页之后,点击预览数据立刻呈现出来了。这里我的测试数据有一万多行,那么当后端传回的数据超过一万或者更多时,前期加载时间则会更长。

总结一下,当我们面对大数据量加载的场景时,分页加载设计思路可以让数据更快地呈现在用户眼前。