如何使用 `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 无关,而是与角度变化检测有关,对吧?我的意思是,&lt;div&gt; 本身不会被重新绘制(删除然后再次添加),但&lt;div&gt; 的内容无论如何都会得到更新,因为在您绑定的模板 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;

让我们使用*ngForarray显示到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 数组进行排序

trackBy 与异步管道

ngFor 块内模板中的异步管道触发 http GET 调用循环

ngFor遍历时 input显示为空,但是ng-model有值

如何为非 li 元素申请 ngfor?

如何对 *ngFor 应用数量限制? [复制]