Angular 大列表性能优化

引言

今天看到一篇文章,有关在Angular应用程序中,如何优化大列表的方法。当数据列表变得很大时,很多Web应用程序都会出现性能问题,作者认为这通常不是框架本身的问题,即框架是框架,代码不是好代码。很多呈现数据的代码没有很好地斟酌,导致了性能问题。下面来看具体的优化方案。

方法一:使用虚拟滚动条(Virtual Scrolling)

虚拟滚动条是Angular CDK实现的一个组件,它可以用来比较高效地呈现数据量大的列表。虚拟滚动的原理是,将列表的容器的高度设置为所有将要呈现数据项的总高度,但是只呈现在当前视图内的一部分数据。我们从这里的例子可以看到,随着滚动条滚动,列表容器中呈现的列表项在不断变化,但是右侧的滚动条并不改变,只是改变了当前容器视图中的项目,并不是真的滚动。

<cdk-virtual-scroll-viewport itemSize="10000">       
 <div *cdkVirtualFor="let item of items">
   {{ item }}
 div>
cdk-virtual-scroll-viewport>

当然,虚拟滚动也有一些缺陷。比如,被隐藏的列表项并没有呈现,因此是不可搜索的,因为这里的“不可见”是指项目元素根本不在页面上,而不仅仅是CSS样式上的不可见。另一个缺陷是,这个虚拟滚动条的实现十分依赖于你项目的特定实现,你的组件实现如果很复杂,可能虚拟滚动条就不能正常工作了。另外,引入这个Angular CDK库,也会增加项目代码包的大小。

方法二:手动呈现

所谓“自动”呈现,是指用Angular提供的原生Directive *ngFor 来实现,而“手动”方式,则是直接调用Angular的API去呈现。比如用ngFor的实现如下:

<tr *ngFor="let item of items; trackBy: trackById;" class="h-12">
  <td>
    <span>{{ item.label }}span>
  td>
tr>

改成用Angular API的实现如下:

<tbody>
  <ng-container #itemsContainer>ng-container>
tbody>
<ng-template #item let-item="item">
  <tr class="h-12">
    <td>
      <span>{{ item.label }}span>
    td>
  tr>
ng-template>

列表的容器和模板定义如下。同时,我们可以利用 ViewContainerRef上的 createEmbeddedView 方法创建视图,针对每个列表项,创建一个模板项,然后作为容器的一部分呈现:

@ViewChild('itemsContainer', { read: ViewContainerRef }) container: ViewContainerRef;
@ViewChild('item', { read: TemplateRef }) template: TemplateRef<any>;

//...
for (let n = start; n <= end; n++) {
    this.container.createEmbeddedView(this.template, {
      item: {
        label: Math.random()
      }
    });
}
//...

关于Angular容器和模板的用法可以参考官方文档,或者这篇文章。采用上述实现后,对于10000规模的列表,脚本运行时间减少了近1/4

方法三:逐步呈现(Progressive Rendering)

逐步呈现的思路是,先呈现列表的一个子集,推迟其他列表项的呈现。这需要用到一些事件循环(Event Loop)相关的函数。比如,我们可以利用 setInterval 函数,每10ms执行一次手动呈现,每次呈现100项,当所有项都呈现时,终结事件循环:

//...
  let index = 0;
  const interval = setInterval(() => {
    const next = index + 100;
    for (let n = index; n <= next ; n++) {
      if (n >= length) {
        clearInterval(interval);
        break;
      }
      const item = {
        item: {
          label: Math.random()
        }
      };
      this.container.createEmbeddedView(this.template, item);
    }
    index += 100;
  }, 10);
//...

总结起来,后面两种方法是依赖项目特点的,并且是需要手动调用Angular API的,这可能引入一些bug,需要特殊处理。一般情况下,使用官方的Angular CDK库就可以满足很多需求了。

参考资料

  • https://blog.bitsrc.io/3-ways-to-render-large-lists-in-angular-9f4dcb9b65
  • https://blog.bitsrc.io/top-reasons-why-your-angular-app-is-slow-c36780a0a289
  • https://material.angular.io/cdk/scrolling/overview
  • https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet/

你可能感兴趣的:(Angular)