2020年,前端框架变得更好,更高效,更快。但是,即使考虑到这一点,即使对于最快的现有框架,在不冻结浏览器的情况下呈现大型列表仍然是一项艰巨的任务。这是“框架快速而代码缓慢”的情况之一。
有许多方法可让您显示大量元素,而不会阻止用户与网页的交互。
这篇文章的作者(我们今天将要翻译的译本)希望探索在网页上显示大列表的现有方法,并讨论它们的应用领域。
尽管此材料针对的是Angular,但此处讨论的内容适用于其他使用纯JavaScript编写的框架和项目。特别是,此处将考虑以下呈现大型列表的方法:- 虚拟滚动(使用Angular CDK)。
- 手动渲染。
- 渐进式渲染。
虚拟滚动
虚拟滚动可能是处理大型列表的最有效方法。是的,他有一些缺点。借助Angular CDK和其他插件,虚拟滚动非常容易在任何组件中实现。虚拟滚动背后的想法很简单,但并不总是易于实现。最重要的是,我们有一个容器和一个元素列表。仅当元素在容器的可见区域内时才渲染它。我们将使用scrolling
Angular CDK中的模块,该模块旨在组织虚拟滚动。为此,您首先需要安装CDK:npm i @angular/cdk
然后,您需要导入模块:import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
...
imports: [ ScrollingModule, ...]
})
export class AppModule {}
之后,您可以在组件中使用cdk-virtual-scroll-viewport
:<cdk-virtual-scroll-viewport itemSize="50">
<div *cdkVirtualFor="let item of items">
{{ item }}
</div>
</cdk-virtual-scroll-viewport>
这是使用此方法组织虚拟滚动的示例项目。如您所见,标准的Angular机制可以轻松实现令人印象深刻的结果。示例中的组件呈现了数千个元素,没有任何问题。如果虚拟滚动效果很好,并且易于实现,那么就会出现一个问题,即为什么要研究其他方法来渲染大型列表。我对这个问题也很感兴趣。事实证明,这种情况有几个原因:- , , . , . ,
Autocomplete
( ). , , , , . — . - — , .
- 虚拟滚动会导致列表内容的可访问性和易用性方面存在一些问题。隐藏的元素不会呈现-这意味着屏幕阅读器将无法使用它们,也无法使用标准的浏览器机制在页面上找到它们。
虚拟滚动是在许多情况下的理想选择(前提是它可以工作):- 在这种情况下,如果要显示事先不知道大小的列表,或者可能要显示的列表可能很大(根据粗略估计,列表包含超过5000个元素,但这在很大程度上取决于每个元素的复杂性)。
- 如果您需要组织无休止的滚动。
手动渲染
我尝试加速大量元素输出的列表的一种处理方法是改用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>
为了衡量10,000个简单元素的渲染性能,我使用了一个基于js-frameworks-benchmark的基准。首先,我检查了列表的性能,该列表使用常规循环输出*ngFor
。结果,脚本花费了1099毫秒,渲染(Rendering)花费了1553毫秒,绘画(Painting)花费了3毫秒。研究列表的性能,它使用* ngFor来显示输出,可以使用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>;
生成数据时,我们使用createEmbeddedView
实体方法渲染它们ViewContainerRef
: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毫秒。实际上,研究使用Angular API输出 True的列表的性能,这意味着该列表仍然非常慢。当您单击适当的按钮时,浏览器“冻结”几秒钟。如果它出现在生产中,用户肯定会不喜欢它。外观如下所示(这里,我用鼠标模仿了加载指示器)。现在,列表变慢了,为了改善手动列表渲染,我们将尝试应用渐进式渲染技术。渐进式渲染
渐进式渲染的想法很简单。它包括逐步渲染元素的子集,从而延迟事件循环中其他元素的渲染。这使浏览器可以显示所有元素,而无需“放慢速度”。以下实现渐进式列表呈现的代码非常简单:- 首先,使用this
setInterval
,我们每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);
请注意,每次调用要传送的功能时显示的元素数量setInterval
以及调用此功能的频率完全取决于具体情况。例如,如果显示的列表中的元素非常复杂,那么一遍中500个这样的元素的输出速度将非常慢。通过测量该解决方案的性能,我得到的结果看上去比之前收到的结果差。代码执行-907毫秒,渲染-2555毫秒,绘图-16毫秒研究列表的性能,列表的输出使用渐进式渲染,但是使用此列表的用户将比以前拥有更多愉悦的感觉。尽管渲染列表所需的时间增加了,但用户不会注意到它。我们一口气渲染了500个元素。同时,在容器边界之外执行渲染。在此,由于容器在渲染期间更改大小或内容滚动的位置发生更改,可能会出现一些问题。如果发生这种情况,您将不得不处理类似的问题。这是处理此类列表的样子。列表很快摘要
在某些情况下,大型列表的手动和渐进式渲染技术当然很有用。在由于某些原因导致虚拟滚动不适合我的情况下,我使用了它们。鉴于以上所述,我们可以说,基于良好的库(例如Angular CDK)构建的大多数虚拟滚动是显示大列表的最佳方法。但是,如果由于某种原因无法使用虚拟滚动,则开发人员还有其他可能性。亲爱的读者们!如何在Web项目中输出大列表?