使用trackBy和ngFor提高Angular的效率

19
在Angular中,*ngFor是否需要使用trackBy函数?在这里(链接)(链接)(链接)(链接)等一些文章中提到,使用trackBy会提高性能并具有更好的内存管理。但我想知道,如果trackBy会带来这样的改进,那么为什么它不是默认行为呢?它是默认行为吗?我看到的东西都过时了吗?
如果它不是默认行为,我的项目中有大约90个组件,每个组件中都有一个*ngFor,请问有没有一种方法可以在不包含以下函数的情况下使用trackBy 90次?我还想避免添加服务并导入90次。
<mat-option *ngFor="let l of list; trackBy: trackByFn" [value]="l.id">
   {{l.name}}
</mat-option>

TS

trackByFn(index, item) {
    return index
}
4个回答

21
请注意,除了一个不可靠的 medium 文章之外,您的示例都没有使用索引,它们都使用对象的唯一标识符,Angular 不可能知道除非您告诉它。
仅返回索引本身有一个用例,但这是相当罕见的。这基本上是告诉 Angular 永远不要重新渲染此列表中现有的项目,因为给定项目的索引永远不会更改。这通常对开发人员来说是非常意外的行为,因为子组件的初始化生命周期钩子将不会重新执行。但是,对于没有子组件的 ngFor,通常可以这样做,但这些类型的列表通常更加高效,并且除非列表非常长或经常更改,否则不会看到太多好处。
trackBy 的想法是允许您重新初始化需要重新初始化的列表项,而不是重新初始化不需要重新初始化的列表项。它不是像一些人处理它那样盲目提高性能的万金油,它的目的和功能应该被完全理解。请记住,仅因为项目具有唯一 ID 并不意味着在 trackBy 函数中使用它是合适的。trackBy 的作用是告诉 Angular 何时需要重新渲染项目,即当我需要那些生命周期钩子重新运行时。如果 ID 保持不变但内容可能会更改,根据您构建某个组件的方式,该组件可能仍然需要重新初始化。

2
好的,假设我使用return item.id。由于它是唯一标识符,那么我应该会看到一些内存/性能上的提升吗?如果确实如此,有没有更简洁的方法在每个组件中使用trackBy而不创建一个函数? - rhavelka
3
如果你的ngFor中的items都有id属性,那么使用item.id肯定会提高性能,因为Angular就知道什么时候需要重新渲染项目,什么时候不需要。但是,如果items没有id属性(像许多items一样),那么这显然会出问题。据我所知,没有全局设置trackBy的方法,而且在某些情况下需要覆盖它,因此也没有太多意义。你可以创建一种基本的“ListComponent”,其中包含默认的trackByFn,以实现更接近目标的效果,但是在模板中仍然需要显式声明其用法。 - bryan60
trackBy 如何知道特定对象发生了更改,即使对象的引用相同,内容已更改? - jeanl
trackBy函数在鼠标移动时被递归调用,这是有效的吗? - Umesh Patadiya
我不确定你的意思。你的 trackBy 不应该是一个复杂的函数,而应该返回一个简单的值。 - bryan60
显示剩余4条评论

8

给它一个测试的类

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

并添加一个指令来监视其初始化和销毁

@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>

enter image description here

很明显,每次更改数组时,Angular都必须相应地重新创建组件。这次让我们尝试使用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;
  }

enter image description here

组件正在被重复使用。因此,我们可以得出结论:使用trackBy可以避免在HTML中创建相同的组件,从而节省工作量。

6

2

一种优化的解决方案:

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


解决方案:通常我们在滚动时滚动视口,这涉及到删除和添加相同数量的视图。与其删除视图并重新创建它们,我们将它们回收利用。因此,我们分离视图,删除上下文,并将它们缓存,以便我们可以在添加周期上附加它们并重新上下文化它们。从而节省了大量的脚本/渲染周期(在Android中称为Recycler View)。

以下是一些Angular库,完全按照所述进行操作,比trackBy函数具有更好的性能:

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

  2. https://github.com/rintoj/ngx-virtual-scroller(此库已不再维护,不适用于最新版本的Angular,但如果您仍想使用,请将其复制粘贴到您的源代码中并导入到您的模块中)

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


访问链接时出现404错误。 - rhavelka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接