如何在ngFor中使用trackBy

Max*_*kyi 32 angularjs-track-by ngfor angular

我真的不明白我应该从什么地方回来trackBy.根据我在网上看到的例子,我应该返回对象上某些属性的值.这是对的吗?为什么我将索引作为参数?

例如,在以下情况中:

constructor() {
    window.setInterval(() => this.users = [
            {name: 'user1', score: Math.random()},
            {name: 'user2', score: Math.random()}],
        1000);
}

userByName(index, user) {
    return user.name;
}

...

<div *ngFor="let user of users; trackBy:userByName">{{user.name}} -> {{user.score}}</div>
Run Code Online (Sandbox Code Playgroud)

尽管名称未更改,但模板中显示的对象仍会更新.为什么?

Max*_*kyi 39

在每次ngDoCheck触发ngForOf指令时,Angular会检查哪些对象已更改.它对此过程使用不同,每个都使用不同的trackBy函数来比较当前对象和新对象.默认trackBy功能按标识跟踪项目:

const trackByIdentity = (index: number, item: any) => item;
Run Code Online (Sandbox Code Playgroud)

它接收当前项并应返回一些值.然后将函数返回的值与此函数上次返回的值进行比较.如果值更改,则差异会报告更改.因此,如果默认函数返回对象引用,则在对象引用已更改时,它将与当前项不匹配.因此,您可以提供trackBy将返回其他内容的自定义函数.例如,对象的一些键值.如果此键值与前一个键值匹配,则Angular将不会检测到该更改.

...trackBy:userByName不再支持该语法.您现在必须提供功能参考.这是基本的例子:

setInterval( () => {
  this.list.length = 0;
  this.list.push({name: 'Gustavo'});
  this.list.push({name: 'Costa'});
}, 2000);

@Component({
  selector: 'my-app',
  template: `
   <li *ngFor="let item of list; trackBy:identify">{{item.name}}</li>
  `
})
export class App {
  list:[];

  identify(index, item){
     return item.name; 
  }
Run Code Online (Sandbox Code Playgroud)

虽然对象引用更改,但DOM不会更新.这是掠夺者.如果你很好奇如何ngFor在幕后工作,请阅读这个答案.

  • @YashwardhanPauranik,如果函数是纯的,即只是根据输入返回结果,我不明白为什么你不能 (3认同)

Cur*_*rse 32

由于这个话题仍然很活跃并且很难找到明确的答案,所以除了@Max 的答案之外,让我添加几个例子:

app.component.ts

   array = [
      { "id": 1, "name": "bill" },
      { "id": 2, "name": "bob" },
      { "id": 3, "name": "billy" }
   ]

   foo() {
      this.array = [
         { "id": 1, "name": "foo" },
         { "id": 2, "name": "bob" },
         { "id": 3, "name": "billy" }
      ]
   }

   identify(index, item) {
      return item.id;
   }
Run Code Online (Sandbox Code Playgroud)

让我们array使用*ngFor.

应用程序组件.html

的实施例*ngFor ,而不trackBy

<div *ngFor="let e of array;">
   {{e.id}} - {{e.name}}
</div>
<button (click)="foo()">foo</button>
Run Code Online (Sandbox Code Playgroud)

如果我们点击foo按钮会发生什么?

? 3 个 div 将被刷新。自己尝试一下,打开控制台进行验证。

的实施例*ngFor 与trackBy

<div *ngFor="let e of array; trackBy: identify">
   {{e.id}} - {{e.name}}
</div>
<button (click)="foo()">foo</button>
Run Code Online (Sandbox Code Playgroud)

如果我们点击foo按钮会发生什么?

? 只有第一个 div 会被刷新。自己尝试一下,打开控制台进行验证。

如果我们更新第一个对象而不是整个对象呢?

   foo() {
      this.array[0].name = "foo";
   }
Run Code Online (Sandbox Code Playgroud)

? 这里没有必要使用trackBy

它在使用Subscription时特别有用,它通常看起来像我用array. 所以它看起来像:

   array = [];
   subscription: Subscription;

   ngOnInit(): void {
      this.subscription = this.fooService.getArray().subscribe(data => {
         this.array = data;
      });
   }

   identify(index, item) {
      return item.id;
   }
Run Code Online (Sandbox Code Playgroud)

从文档:

为了避免这种昂贵的操作,您可以自定义默认跟踪算法。通过向 NgForOf 提供 trackBy 选项。trackBy 接受一个有两个参数的函数:index 和 item。如果给定了 trackBy,Angular 会根据函数的返回值跟踪变化。

在此处阅读更多信息:https : //angular.io/api/common/NgForOf

在这里找到我的原始答案:https : //stackoverflow.com/a/57890227/9753985

  • 我真的不明白,为什么在第二种情况下只有第一个对象被更新(使用“trackBy”)。用于检测变化的“identify”方法返回“item.id”,在按下按钮之前和之后该值保持“1”。仅名称从“bill”更改为“foo”,但由于未比较名称,因此该方法不应检测到任何更改。我预计在这种情况下不会有任何更新。我会使用“item.name”,但这似乎是错误的方法。我在这里缺少什么?有人可以澄清一下吗? (8认同)
  • @Thomas `identify` 用于识别 `*ngFor` 数组中的对象 → 您必须选择一个静态值,例如 id。如果您想了解有关 ngForTrackBy 的更多信息,请查看此处 https://angular.io/api/common/NgForOf#ngForTrackBy。 (2认同)

Rém*_*ery 22

以下是我在项目中使用的内容,允许通过迭代模型的属性进行跟踪,而无需在组件的类中编写函数的麻烦:

import { Host, Directive, Input } from "@angular/core";
import { NgForOf } from "@angular/common";

@Directive({
    selector: "[ngForTrackByProperty]"
})
export class TrackByPropertyDirective {

    private _propertyName: string = "";

    public constructor(@Host() private readonly _ngFor: NgForOf<any>) {
        this._ngFor.ngForTrackBy = (_: number, item: any) => this._propertyName ? item[this._propertyName] : item;
    }

    @Input("ngForTrackByProperty")
    public set propertyName(value: string | null) {
        // We must accept null in case the user code omitted the ": 'somePropName'" part.
        this._propertyName = value ?? "";
    }

}
Run Code Online (Sandbox Code Playgroud)

用法 :

<some-tag *ngFor="let item of models; trackByProperty: 'yourDiscriminantProp'">

Run Code Online (Sandbox Code Playgroud)


Azh*_*ikh 5

应用程序组件.html

<button class="btn btn-warning" (click)="loadCourses()">LoadCourses</button>
<ul>
    <li *ngFor="let course of courses; trackBy:trackCourse">
        {{course.name}}
    </li>
</ul>
Run Code Online (Sandbox Code Playgroud)

应用程序组件.ts

loadCourses() {

    this.courses = [
    {id:1, name:'cour1'},
    {id:2, name:'cour2'},
    {id:3, name:'cour3'}
    ]  
};

trackCourse(index : number, course: any) {
    return course ? course.id : undefined;
};
Run Code Online (Sandbox Code Playgroud)

Mosh 的 参考代码您可以在指令部分找到


小智 5

使用 trackBy 的目的是设置可迭代中元素的标识。如果 Angular 看到两个具有相同标识的元素,它将继续检查元素的内容,并且只有在内容发生更改时才会重新绘制/重新渲染。如果没有标识,Angular 将依赖元素的对象引用,即使内容相同,这些元素通常也会发生变化,因此 Angular 会因为不同的引用而重新绘制/重新渲染元素。