如何在‘ ngFor’中使用‘ trackBy’

我真的不明白我应该从 trackBy返回什么。基于我在 web 上看到的一些例子,我应该返回对象上某些属性的值。对吗?为什么我应该得到 index作为一个参数?

例如,在下列情况下:

组件

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>

此模板中显示的对象仍在更新,尽管名称没有改变。为什么?

202794 次浏览

这个角度的 NgFor文件将帮助你

下面的代码示例

<div *ngFor="let user of users; trackBy:user?.name">
\{\{user.name}} -> \{\{user.score}}
</div>

在为 ngForOf指令触发的每个 ngDoCheck上,Angular 检查哪些对象已经更改。对于这个过程,它使用不同的方法,每个不同的方法都使用 trackBy函数来比较当前对象和新对象。默认的 trackBy函数根据标识跟踪项目:

const trackByIdentity = (index: number, item: any) => item;

它接收当前项并应返回一些值。然后将函数返回的值与该函数上次返回的值进行比较。如果值更改,则不同的值报告更改。因此,如果默认函数返回对象引用,则如果对象引用已更改,则该函数将不匹配当前项。因此,您可以提供自定义的 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;
}

虽然对象引用发生了变化,但 DOM 没有更新。给你柱塞。如果你好奇 ngFor在引擎盖下是如何工作的,看看这个答案

由于这个主题仍然是活跃的和找到一个明确的答案是困难的,让我增加一些例子,除了 @ Max的答案:

应用程序组件

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;
}

让我们使用 *ngForarray显示为3个 div。

App.Component. html

例子 *ngFor 没有追踪器:

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

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

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

例子 *ngFor 用 trackBy:

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

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

→只刷新第一个 div

如果我们更新第一个对象而不是整个对象会怎么样?

foo() {
this.array[0].name = "foo";
}

→这里不需要使用 trackBy

这是特别有用的时候使用一个 订阅,往往看起来像我与 array的示意图。所以看起来就像:

 array = [];
subscription: Subscription;


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


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

根据文件:

为了避免这种昂贵的操作,您可以自定义默认值 通过向 NgForOf 提供 trackBy 选项来实现跟踪算法。 TrackBy 接受一个有两个参数的函数: index 和 item 给定 trackBy 时,Angular 将通过 功能。

阅读更多: https://angular.io/api/common/NgForOf

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

其他你可以利用的东西

*ngFor="a of array; index as i;"

还有

[attr.data-target]="'#test' + i"

还有

name="test\{\{i}}

下面是我在项目中使用的方法,它允许通过迭代模型的属性进行跟踪,而不必在组件的类中编写函数:

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 ?? "";
}


}

用法:

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


App.Component. html

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

应用程序组件

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;
};

参考文献 代码与摩西 你可以在指示部找到

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