【问题标题】:How to use `trackBy` with `ngFor`如何将 `trackBy` 与 `ngFor` 一起使用
【发布时间】:2017-02-08 08:35:57
【问题描述】:

我真的不明白我应该从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 进来的地方工作的

标签: angular ngfor angularjs-track-by


【解决方案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://stackoverflow.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】:

    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>
    

    app.component.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;
    };
    

    参考 Mosh 编码 You can find in Directives Section

    【讨论】:

      【解决方案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 不与回调函数一起使用的示例,而是通过直接传递迭代项的属性。你能以某种方式确认这种用法吗?
      【解决方案6】:

      你也可以使用

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

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

      name="test{{i}}
      

      【讨论】:

      猜你喜欢
      • 2017-06-25
      • 1970-01-01
      • 2019-03-17
      • 2020-01-15
      • 2018-05-12
      • 1970-01-01
      • 2018-11-18
      • 2023-04-03
      • 1970-01-01
      相关资源
      最近更新 更多