如何使用 `trackBy` 和 `ngFor`
Posted
技术标签:
【中文标题】如何使用 `trackBy` 和 `ngFor`【英文标题】:How to use `trackBy` with `ngFor` 【发布时间】:2017-06-25 18:45:15 【问题描述】:我真的不明白我应该从trackBy
返回什么。根据我在网上看到的一些示例,我应该返回对象上某些属性的值。这样对吗?为什么我要获取index
作为参数?
比如下面这种情况:
Component.component.ts
constructor()
window.setInterval(() => this.users = [
name: 'user1', score: Math.random() ,
name: 'user2', score: Math.random()
],
1000);
userByName(index, user)
return user.name;
Component.template.html
<div *ngFor="let user of users; trackBy:userByName">
user.name -> user.score
</div>
尽管名称未更改,但此模板中显示的对象仍会更新。为什么?
【问题讨论】:
你试过写let user of users; let index=index; trackBy:userByName(index,user)
吗?
您可以返回索引,该索引对于每个项目都是唯一的。
@micronyks,我不明白。你能详细说明吗?举个例子吧?
你不明白什么?你能告诉我吗?我告诉过你返回索引就可以了。
我不明白 ngFor 的比较机制是如何在 ngTrackBy
进来的地方工作的
【参考方案1】:
在为ngForOf
指令触发的每个ngDoCheck
上,Angular 都会检查哪些对象发生了变化。它为此过程使用了不同的方法,每个不同的方法都使用 trackBy
函数将当前对象与新对象进行比较。默认的trackBy
函数按身份跟踪项目:
const trackByIdentity = (index: number, item: any) => item;
它接收当前项目并且应该返回一些值。然后将函数返回的值与此函数上次返回的值进行比较。如果值发生变化,diff 报告变化。因此,如果默认函数返回对象引用,则如果对象引用发生更改,它将与当前项不匹配。因此,您可以提供您的自定义 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 并未更新。 Here is 笨蛋。如果您对 ngFor
的工作原理感到好奇,read this answer。
【讨论】:
我可以使用相同的trackByFn
进行 2 个循环吗?
@YashwardhanPauranik,如果函数是纯函数,即仅根据输入返回结果,我不明白为什么不能
我无法在 trackbyfn 中访问这个(组件本身)。有什么办法吗?
@MaximKoretskyi 您的问题“尽管名称未更改,但模板中显示的对象仍在更新。为什么?”实际上与trackBy
无关,而是与角度变化检测有关,对吧?我的意思是,<div>
本身不会被重新绘制(删除然后再次添加),但<div>
的内容无论如何都会得到更新,因为在您绑定的模板 user.score
会改变每个间隔滴答声。
@GlennMohammad 的声明是有道理的。 TrackBy 只回答“是否删除并重新创建 DOM 元素?”的问题。模板标签内的 content 将通过 for 循环渲染**不管 trackBy 返回的值(**只要 DOM 存在)。 ----- 当 trackBy 告诉 angular 在不破坏 DOM 的情况下渲染更改时,大多数效率会占上风。 ----- AFAIU比较也包括索引;像“ ( index, tackBy(index, item) ) ”。 ----- 即:当前索引 lastTime 的 trackBy 返回值是多少。【参考方案2】:
由于这个话题仍然很活跃,而且很难找到明确的答案,所以除了@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;
让我们使用*ngFor
将array
显示到3个div中。
app.component.html
*ngFor
的示例没有 trackBy:
<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
。
在使用 Subscription 时特别有用,它通常看起来像我用 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://***.com/a/57890227/9753985
【讨论】:
我不太明白,为什么在第二种情况下只更新第一个对象(使用trackBy
)。用于检测更改的identify
方法返回item.id
,在按下按钮之前和之后保持1
。只是名称从bill
更改为foo
,但由于没有比较名称,因此该方法应该检测不到任何变化。我预计在这种情况下不会更新任何内容。我会使用item.name
,但这似乎是错误的方法。我在这里想念什么?有人可以澄清一下吗?
@Thomas identify
用于从 *ngFor
数组中识别对象 → 您必须选择像 id 这样的静态值。如果您想了解有关 ngForTrackBy 的更多信息,请查看此处angular.io/api/common/NgForOf#ngForTrackBy。
我在 trackBy 函数中记录了索引和项目,它正在不间断地记录。这正常吗?
@PrajilShrestha 是的,这是正常行为,它与 trackBy
无关,它与 Angular 循环 (angular.io/guide/lifecycle-hooks) 及其变化检测 (angular.io/api/core/ChangeDetectorRef) 有关。
很好的解释谢谢【参考方案3】:
这是我在我的项目中使用的,它允许通过迭代模型的属性进行跟踪,而无需在组件的类中编写函数:
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'">
【讨论】:
【参考方案4】:你也可以使用
*ngFor="a of array; index as i;"
和
[attr.data-target]="'#test' + i"
和
name="testi
【讨论】:
这些不是trackBy
。请参阅angular.io/api/common/NgForOf#ngForTrackBy。【参考方案5】:
这个有角度的NgFor
文档会帮助你。 https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html
以下代码示例
<div *ngFor="let user of users; trackBy:user?.name">
user.name -> user.score
</div>
【讨论】:
我似乎找不到trackBy
不与回调函数一起使用的示例,而是通过直接传递迭代项的属性。你能以某种方式确认这种用法吗?以上是关于如何使用 `trackBy` 和 `ngFor`的主要内容,如果未能解决你的问题,请参考以下文章
在 Angular 4 中使用 trackBy 对 *ngFor 数组进行排序
ngFor 块内模板中的异步管道触发 http GET 调用循环