如何为大型阵列加速ngFor? [英] How can I speed up ngFor for a large array?

查看:90
本文介绍了如何为大型阵列加速ngFor?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发 Pokedex网站,由于现在有721个Pokemon,ngFor正在采用长时间显示第一次输入所有条目.加载完所有数据后,将它们真正放入DOM中似乎要花费大约2400毫秒.

I'm working on a Pokedex site and thanks to there now being 721 Pokemon an ngFor is taking a long time to display all the entries the first time. Once I have all the data loaded it seems to be taking ~2400ms to actually put them in the DOM.

这是有问题的ngFor:

Here's the ngFor in question:

<entry *ngFor="let p of (pokedex | filter:search:SelectedVer), let i = index, let last = last"
    [id]="'pokemon-entry-' + p.id"
    [pokemon]="p"
    [language]="SelectedLang"
    (click)="SelectPokemon(p)"></entry>

我在Chrome的开发工具中运行了一个时间表,并得到了如下所示的内容:

I ran a timeline in Chrome's dev tools and got something that looks like this:

我对时间轴没有太多经验,但是在我看来,中间有一个很大的块(顶部标记为XHR Load (/csv/pokemon_game_indices.csv)).根据时间轴,ajax调用本身需要0.02毫秒.我假设使这个很大的块的原因是ajax请求完成后发生的更改检测.那就是当我拿起我一直在构建的模型并将它们放在ngFor上面使用的pokedex变量中时.我对时间轴的理解是,ngFor要添加的721个DOM元素的构建大约需要2.5s的时间.

I don't have much experience with the timeline but it seems to me that there's way too big a block right there in the middle (the top is labeled XHR Load (/csv/pokemon_game_indices.csv)). The ajax call itself takes 0.02 ms according to the timeline. I'm assuming what makes this such a large block is the change detection that happens after the ajax request is complete. That's when I take my models that I've been building and put them in the pokedex variable that the ngFor uses above. My understanding of the timeline is that the construction of the 721 DOM elements to be added by the ngFor is taking about 2.5s to complete.

我确实尝试将我的entry组件仅分解为html(该组件实际上不执行任何操作),但这似乎并没有以任何明显的方式影响时间.删除我用来过滤列表的管道也不会影响时间.

I did try un-componentizing my entry component into just the html (the component really doesn't do anything) but that doesn't seem to impact the time in any noticeable way. Removing the pipe I use to filter the list also doesn't impact the time.

有没有办法加快ngFor的速度?

Is there a way to speed up this ngFor?

我正在使用Angular 2 RC1.我正在启用产品模式.我正在Chrome 51.0.2704.79 m中运行此程序

I'm using Angular 2 RC1. I am enabling prod mode. I'm running this in Chrome 51.0.2704.79 m

推荐答案

简短而甜美的回答是不要遍历整个数组",但这对我来说还不够好.我希望它看起来像是存在整个条目列.因此,我在上面放置了一个空格,ngFor遍历了数组的一个子节,在下面放置了一个空格,这一起使列表看起来就像所有元素一直存在.

The short and sweet answer is "don't iterate over the entire array" but that wasn't good enough for me. I wanted it to look like the entire column of entries was present. So I put a spacer above, the ngFor iterates over a subsection of the array, and a spacer below and together this makes the list look like all the elements are there all the time.

这是我的html的简化版本,仅包含此问题的相关部分(

Here's a simplified version of my html with only the relevant parts to this problem (full example on bitbucket):

<div (scroll)="ColScroll($event)">
  <div [style.height]="Math.max(0, Math.max(0, scrollPos - 10) * 132)"></div>
  <entry *ngFor="let p of (base.pokemon | filter:search:SelectedVer:SelectedLang) | justafew:scrollPos" [pokemon]="p"></entry>
  <div [style.height]="Math.max(0,((base.pokemon | filter:search:SelectedVer:SelectedLang).length - scrollPos - 40)) * 132"></div>
</div>

绝对清晰的超小型结构:

Ultra-minimal structure for absolute clarity:

<div> <!-- column -->
  <div></div> <!-- spacer -->
  <entry *ngFor='...'></entry>
  <div></div> <!-- spacer -->
</div>

首先,一个非常关键的点:<entry>始终总是正好为120像素高,底部边缘为12像素,总计总空间为132像素. CSS做到了这一点.无论我要选择哪种恒定大小,它都可以使用,但是我做出特殊的假设,即大小恰好是132像素.

First, a very key point: <entry> is always exactly 120 pixels tall with a 12 pixel bottom margin totaling 132 pixels of total space. The CSS makes this absolute. This works for whatever constant size I wanted to pick, but I make special assumptions that the size is exactly 132 pixels.

简短的版本是,当您滚动列的scrollHeight时,将确定实际应显示在屏幕上的条目.如果ngFor实际构建的前10个元素不在屏幕上,则第一个可见元素从数字11开始.我占一个4k屏幕,并显示40个条目(占用5280个像素),以确保整个列看起来都是完整的.然后,滚动条看起来正确,我在40个条目下面有一个间隔,以强制div具有适当的可滚动高度.这是视觉上发生的情况的图像:

The short version is that as you scroll the column's scrollHeight determines which entries should actually be on screen. If the first 10 elements that the ngFor actually builds are off screen then the first visible element begins at number 11. I account for a 4k screen and show 40 entries (taking up 5280 pixels) to be sure that the entire column looks full. Then, so the scrollbar looks correct, I have a spacer below the 40 entries to force the div to have the proper scrollable height. Here's an image of what's visually going on:

以下是控制器中的相关变量和函数(

Here's the relevant variables and functions in the controller (bitbucket):

scrollPos = 0;
...
ColScroll(event: Event) {
  let pos = $(event.target).scrollTop();
  this.scrollPos = Math.floor(pos / 132);
}

在这里使用jQuery使我丧命,但是我已经将它用于其他用途,因此我需要跨浏览器. scrollPos保持我应该在屏幕上显示的第一项的第一个索引.

It kills me to use jQuery here but I was already using it for something else and I needed something cross-browser. scrollPos holds the first index of the first item that I should be showing on screen.

实际构建所有<entry>元素的ngFor看起来像这样:

The ngFor that actually builds all the <entry> elements looks like this:

*ngFor="let p of (base.pokemon | filter:search:SelectedVer:SelectedLang) | justafew:scrollPos"

打破现状:

base.pokemon是创建每个入口元素所需的口袋妖怪数据的数组.

base.pokemon is an array of the pokemon data necessary to create each entry element.

... | filter:search:SelectedVer:SelectedLang)用于搜索列表.我将其留在这里的示例中,以表明您仍然可以在我的黑客入侵之前使用列表.

... | filter:search:SelectedVer:SelectedLang) is used for searching through the list. I leave it in my sample here to show that you can still play with the list before my hack comes into play.

... | justafew:scrollPos是发生魔术的地方.这是整个过滤器( bitbucket ):

... | justafew:scrollPos is where the magic happens. Here's that filter in it's entirety (bitbucket):

import { Pipe, PipeTransform } from '@angular/core';

import { MinPokemon } from '../models/base';

@Pipe({
  name: 'justafew',
  pure: false
})
export class JustAFewPipe implements PipeTransform {
  public transform(value: MinPokemon[], start: number): MinPokemon[] {
    return value.slice(Math.max(0, start - 10), start + 30);
  }
}

scrollPos作为start参数传递.例如,如果我在列中向下滚动了13200像素,则scrollPos将设置为100(请参见上面控制器中的滚动事件).这将对数组进行切片,使其返回从90到130的元素.我想在屏幕上稍微溢出一点,以确保快速滚动不会导致可见的空白(疯狂的快速滚动可能仍会显示它,但是您移动得如此之快很容易想到浏览器的渲染速度还没有这么快,所以我让它滑动了).我使用Math.max,所以我不会使用负数进行切片,例如当我位于列表的顶部并且scrollPos为0时.

scrollPos was passed in as the start parameter. For example, if I've scrolled 13200 pixels down my column then scrollPos would be set to 100 (see the scrolling event in the controller above). This will slice the array so that it returns elements 90 through 130. I want to overflow the screen a little to ensure that fast scrolling won't result in visible white space (insanely fast scrolling might still show it but you're moving so fast it's easy to think that the browser simply hasn't rendered that fast so I let it slide). I use Math.max so I don't slice using negative numbers such as when I'm at the very top of the list and scrollPos is 0.

现在是垫片.他们保持滚动条诚实.我绑定了它们的[style.height]并使用一些数学运算法则来使这些间隔符占用所需的空间.当我向下滚动时,顶部间隔物会变高,而底部间隔物会收缩完全相同的量,因此列始终处于相同的高度.当我向上滚动时,数学结果恰好相反:顶部缩小,底部增长.底部的分隔符使用与ngFor完全相同的过滤器逻辑,因此,如果我运行返回100而不是721 pokemon的搜索,它会调整为100个条目的高度.第一个使用scrollPos - 10的分隔符是因为justafew过滤器返回10.出于相同的原因,底部的分隔符使用了scrollPos - 30,因为这就是返回的justafew数量.

Now the spacers. They keep the scrollbar honest. I bind their [style.height] and use a little math to make these spacers take up the required space. As I scroll down, the top spacer grows taller and the bottom spacer shrinks by the exact same amount so the column is always the same height. When I scroll back up the math works out just the opposite: the top shrinks and the bottom grows. The bottom spacer uses the exact same filter logic as the ngFor so that if I run a search that returns 100 instead of 721 pokemon it adjusts to the height of 100 entries. The first spacer using scrollPos - 10 because the justafew filter goes back 10. For the same reason, the bottom spacer uses scrollPos - 30 because that's how many justafew returns.

我知道它看起来像很多活动部件,但是它们都很简单快捷.不幸的是,到处都有很多相互依赖的魔术数字",但是考虑到性能的提高和可靠性,这使我无法显示整个列表,而让我感到困惑.也许有一天,我将制作一个组件或指令以将其全部放置在一个可配置的位置.

I know it looks like a lot of moving parts but they're all simple and quick. Unfortunately there are a lot of "magic numbers" all over the place that rely on each other but considering the performance improvements and reliability this gave me over showing the entire list I let it slide. Maybe someday I'll make a component or directive to put it all in one configurable place.

更新:大约2 1/2年后,随着Angular 7的发布一些更改网站,并在大约一个小时内完成了虚拟滚动工作.即使有组件回收.我完全建议使用Angular Material进行虚拟滚动.

UPDATE: 2 1/2 years or so later and with Angular 7's release there's now a Angular Material package for virtual scrolling. I made a few changes to my site and got virtual scrolling working in about an hour. Even with component recycling. I thoroughly recommend using Angular Material for virtual scrolling.

这篇关于如何为大型阵列加速ngFor?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆