Angular 高效地使用 trackBy 和 ngFor

Angular efficiently using trackBy with ngFor

在Angular中,*ngFor需要trackBy函数吗?我看到几篇文章 here, here, here, and here 说使用 trackBy 会提高性能并有更好的内存管理。但我想知道 trackBy 是否是这样的改进,那么为什么它不是默认行为?这是默认行为,我正在查看的所有内容都已过时吗?

如果这不是默认行为,我的项目在 90 个组件中有大约 90 个 *ngFor,我想知道是否有一种方法可以在我不包括以下函数的地方使用 trackBy 90 次。我还想避免添加服务并将其导入 90 次。

HTML

<mat-option *ngFor="let l of list; trackBy: trackByFn" [value]="l.id">
   {{l.name}}
</mat-option>

TS

trackByFn(index, item) {
    return index
}

请注意,none 您的示例使用索引(除了一篇不可靠的媒体文章),它们都使用对象的唯一标识符,除非您告诉它,否则 angular 不可能知道.

单独返回索引有一个用例,但这是一个相当不常见的用例。它基本上告诉 angular 不要重新呈现此列表中的现有项目,因为给定项目的索引永远不会改变。这通常是开发人员非常意外的行为,因为子组件的初始化生命周期挂钩不会重新执行。虽然在没有子组件的 ngFor 中这样做通常是安全的,但这些类型的列表通常性能更高,除非列表很长或经常更改,否则您不会看到太多好处。

trackBy 的想法是允许您重新初始化列表中需要它的项目,而不是重新初始化不需要的项目。它并不是像某些人对待的那样盲目提高性能的灵丹妙药,应该充分理解它的目的和功能。请记住,仅仅因为一个项目具有唯一 ID 并不意味着它适合在 trackBy 函数中使用。 trackBy 的目的是告诉 angular 项目何时需要重新呈现,即我何时需要那些生命周期挂钩来重新 运行。如果 ID 保持不变但内容可能会发生变化,具体取决于您构建某个组件的方式,则该组件可能需要重新初始化。

给它一个class进行测试

export class Item {
  id: number;
  name: string;
}

并添加一个指令来监控它的init和destory

@Directive({selector: '[appMonitor]'})
export class MonitorDirective implements OnInit, OnDestroy {


  ngOnInit(): void {
    console.log('init');
  }

  ngOnDestroy(): void {
    console.log('destroy');
  }
}

初始化数组

  itemArray: Item[] = [
    {id: 1, name: 'Tom'},
    {id: 2, name: 'Joe'},
    {id: 3, name: 'KK'}
  ];

改变数组内容的两个函数

  allFoo(): void {
    this.itemArray = [
      {id: 1, name: 'Tom_foo'},
      {id: 2, name: 'Joe_foo'},
      {id: 3, name: 'KK_foo'}
    ];
  }

  allBar(): void {
    this.itemArray = [
      {id: 1, name: 'Tom_bar'},
      {id: 2, name: 'Joe_bar'},
      {id: 3, name: 'KK_bar'}
    ];
  }

准备工作已经完成,到目前为止一切顺利。 首先让我们在没有 trackBy

的情况下进行测试
  <div *ngFor="let item of itemArray " appMonitor>
    Id: {{item.id}} Name:{{item.name}}
  </div>

很明显,每次更改数组 angular 都会重新创建组件 accordingly.Let 的 try trackBy 这次:

<div *ngFor="let item of itemArray ;trackBy:identify" appMonitor>
  Id: {{item.id}} Name:{{item.name}}
</div>

身份:

  identify(index: number, item: Item): number {
    return item.id;
  }

组件正在 resued.So 我们可以得出结论,使用 trackBy 可以节省在 hmtl 中创建相同组件的工作。

我制作了一个动画,展示了 ngForngFortrackBy 如何并排操作 DOM

在此处阅读文章:https://link.medium.com/ckBRk9wrinb

优化方案:

问题:渲染视图是 O(n) 列表的一项昂贵任务


解决方案:我们通常在卷轴上滚动视口,这涉及删除和添加相同数量的视图。我们回收它们,而不是删除视图并重新创建它们。所以我们分离视图,删除上下文并缓存它们,以便我们可以附加它们并在添加周期中 re-context 它们。因此节省了大量 script/render 周期。 (在 android 中称为回收视图)

这里有几个 angular 库完全按照提到的那样做,比 trackBy 函数更好的性能

  1. https://material.angular.io/cdk/scrolling/overview

  2. https://github.com/rintoj/ngx-virtual-scroller(此库已不再维护且不适用于最新的 angular 版本,但如果您仍想使用 copy-paste 到您的源代码并在您的模块中导入)

  3. https://github.com/anagram4wander/ng-vfor-lib