在 Angular 中渲染大型列表的 3 种方法
框架很快,你的代码很慢
使用 Angular 渲染大型项目列表的可用技术概述
本文最初由Giancarlo Buomprisco在Bits and Pieces上发表
2020 年的框架变得更好、更高效、更快速。即便如此,即使对于目前最快的框架来说,在 Web 上渲染大量项目列表而不导致浏览器卡顿仍然很困难。
这是“框架很快,代码很慢”的众多情况之一。
有许多不同的技术可以为用户以非阻塞的方式渲染大量项目。在本文中,我将探讨当前可用的技术,并根据具体的用例选择最佳技术。
虽然本文重点介绍如何使用 Angular 优化渲染,但这些技术实际上也适用于其他框架或简单的 Vanilla Javascript。
框架很快,你的代码很慢
本文详细介绍了我在之前的一篇文章中讨论的一个方面:渲染过多的数据。Angular
应用运行缓慢的主要原因
我们将研究以下技术:
-
虚拟滚动(使用 Angular CDK)
-
手动渲染
-
渐进式渲染
无论你选择哪种实现来渲染长列表,请务必将可复用的 Angular 组件共享到Bit.dev的组件中心。这将节省你重复工作的时间,并使你和你的团队更容易在 Angular 项目中使用经过测试和性能优化的代码。
您可以在我之前的文章中阅读更多相关信息:
使用 Angular 和 Bit 共享组件
*Bit 简介:构建和共享 Angular 组件*blog.bitsrc.io
1.虚拟滚动
虚拟滚动可能是处理大型列表最有效的方法,但有一个限制。得益于Angular CDK和其他插件,它可以很容易地在任何组件中实现。
这个概念很简单,但实现起来并不总是最容易的:
- 给定一个容器和一个项目列表,只有当项目位于容器的可见边界内时,才会渲染该项目
要使用CDK的Scrolling模块,我们首先需要安装该模块:
npm i @angular/cdk
然后,我们导入模块:
import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
...
imports: [ ScrollingModule, ...]
})
export class AppModule {}
我们现在可以使用组件在我们的组件中使用虚拟滚动:
<cdk-virtual-scroll-viewport itemSize="50">
<div *cdkVirtualFor="let item of items">
{{ item }}
</div>
</cdk-virtual-scroll-viewport>
正如你所见,它非常容易使用,而且效果令人印象深刻。该组件可以毫无问题地渲染成千上万个项目。
如果虚拟滚动这么好,而且很容易实现,为什么还要费心探索其他技术呢?这也是我一直想知道的——事实上,原因不止一个。
-
它的工作方式很大程度上取决于具体实现:单一实现很难管理所有可能的场景。
例如,我的组件依赖于自动完成字段(由同一团队构建),但不幸的是,它并没有按预期工作。你的项目越复杂,处理起来就越困难。 -
另一个模块,另一大块代码已添加到您的应用程序中。
-
可访问性和可用性:隐藏的项目不会呈现,因此无法搜索。
虚拟滚动在很多情况下都是理想的(当它起作用时):
-
未定义且可能非常庞大的项目列表(大约超过 5k,但这在很大程度上取决于每个项目的复杂性)
-
无限滚动项目
2. 手动渲染
我尝试过的加速大量项目列表的选项之一是使用 Angular 的 API 手动渲染,而不是依赖 *ngFor。
我们有一个简单的 ngFor 循环模板:
<tr
*ngFor="let item of data; trackBy: trackById; let isEven = even; let isOdd = odd"
class="h-12"
[class.bg-gray-400]="isEven"
[class.bg-gray-500]="isOdd"
>
<td>
<span class="py-2 px-4">{{ item.id }}</span>
</td>
<td>
<span>{{ item.label }}</span>
</td>
<td>
<a>
<button class="py-2 px-4 rounded (click)="remove(item)">x</button>
</a>
</td>
</tr>
我正在使用受js-frameworks-benchmark启发的基准来计算 10000 个简单项目的渲染。
第一次基准测试是用简单、常规的 *ngFor 执行的。结果如下:脚本执行耗时 1099 毫秒,渲染耗时 1553 毫秒,绘画耗时 3 毫秒。
通过使用 Angular 的 API,我们可以手动渲染项目。
<tbody>
<ng-container #itemsContainer></ng-container>
</tbody>
<ng-template #item let-item="item" let-isEven="isEven">
<tr class="h-12"
[class.bg-gray-400]="isEven"
[class.bg-gray-500]="!isEven"
>
<td>
<span class="py-2 px-4">{{ item.id }}</span>
</td>
<td>
<span>{{ item.label }}</span>
</td>
<td>
<a>
<button class="py-2 px-4 rounded" (click)="remove(item)">x</button>
</a>
</td>
</tr>
</ng-template>
控制器的代码改变如下:
- 我们声明我们的模板和容器
@ViewChild('itemsContainer', { read: ViewContainerRef }) container: ViewContainerRef;
@ViewChild('item', { read: TemplateRef }) template: TemplateRef<*any*>;
- 当我们构建数据时,我们还使用ViewContainerRef createEmbeddedView方法对其进行渲染
private buildData(length: number) {
const start = this.data.length;
const end = start + length;
for (let n = start; n <= end; n++) {
this.container.createEmbeddedView(this.template, {
item: {
id: n,
label: Math.random()
},
isEven: n % 2 === 0
});
}
}
结果显示出适度的改善:
- 脚本编写耗时 734 毫秒,渲染耗时 1443 毫秒,绘画耗时 2 毫秒
但实际上,它仍然非常慢!点击按钮时,浏览器会冻结几秒钟,给用户带来糟糕的用户体验。
它看起来是这样的(我正在移动鼠标来模拟加载指示器😅):

现在让我们尝试将渐进式渲染与手动渲染相结合。
3.渐进式渲染
渐进式渲染的概念很简单,就是逐步渲染一部分项目,并推迟事件循环中其他项目的渲染。这使得浏览器能够平滑、渐进地渲染所有项目。
下面的代码很简单:
-
我们创建一个每 10 毫秒运行一次的间隔,并同时渲染 500 个项目
-
当所有项目都已渲染后,根据索引,我们停止间隔并中断循环
private buildData(length: number) {
const ITEMS_RENDERED_AT_ONCE = 500;
const INTERVAL_IN_MS = 10;
let currentIndex = 0;
const interval = setInterval(() => {
const nextIndex = currentIndex + ITEMS_RENDERED_AT_ONCE;
for (let n = currentIndex; n <= nextIndex ; n++) {
if (n >= length) {
clearInterval(interval);
break;
}
const context = {
item: {
id: n,
label: Math.random()
},
isEven: n % 2 === 0
};
this.container.createEmbeddedView(this.template, context);
}
currentIndex += ITEMS_RENDERED_AT_ONCE;
}, INTERVAL_IN_MS);
请注意,渲染项目的数量和间隔时间完全取决于您的具体情况。例如,如果您的项目非常复杂,那么一次渲染 500 个项目肯定会非常慢。
正如您在下面看到的,统计数据看起来确实更糟:
但用户体验并没有变差。尽管渲染列表所需的时间比以前更长,但用户却感觉不到。我们一次渲染 500 个项目,而且渲染发生在容器边界之外。
当容器改变其大小或滚动位置时,可能会出现一些问题,因此在某些情况下需要缓解这些问题。
让我们看看它是什么样子的:

最后的话
上述技术在某些情况下确实很有用,并且每当虚拟滚动不是最佳选择时我都会使用它们。
话虽如此,但在大多数情况下,使用像 Angular 的 CDK 这样的优秀库进行虚拟滚动绝对是处理大型列表的最佳方式。
如果您需要任何澄清,或者您认为某些内容不清楚或错误,请发表评论!
希望你喜欢这篇文章!如果喜欢,请在Medium、Twitter或Dev上关注我,获取更多关于软件开发、前端、RxJS、Typescript 等的文章!
鏂囩珷鏉ユ簮锛�https://dev.to/angular/3-ways-to-render-large-lists-in-angular-10nl