angular ngFor trackBy 没有按我预期的那样工作

Qia*_*hen 7 angular

当我阅读and的文档(https://angular.io/api/common/NgForOf)时,我想我明白如果 trackBy 函数返回的值发生变化,Angular 只会重做 DOM,但是当我玩它在这里(https://stackblitz.com/edit/angular-playground-bveczb),我发现我实际上根本不理解它。这是我的代码的重要部分:ngFortrackBy

export class AppComponent {
  data = [
    { id: 1, text: 'one' },
    { id: 2, text: 'two' },
    { id: 3, text: 'three' },
  ];

  toUpper() {
    this.data.map(d => d.text = d.text.toUpperCase());
  }

  trackByIds (index: number, item: any) {
    return item.id; 
  };
}
Run Code Online (Sandbox Code Playgroud)

和:

<div *ngFor="let d of data; trackBy: trackByIds">
  {{ d.text }}
</div>
<button (click)=toUpper()>To Upper Case</button>
Run Code Online (Sandbox Code Playgroud)

我预料到的点击按钮应该不是单从小写改为上,但它确实。我以为我trackByIdstrackByin 中使用了该函数*ngFor,并且由于trackByIds它只检查id项目的属性,因此更改 id 以外的任何内容都不应导致 DOM 重做。我想我的理解是错误的。

Sim*_*ver 17

如果trackBy似乎不起作用:

1) 确保您为 trackBy 函数使用正确的签名

https://angular.io/api/core/TrackByFunction

interface TrackByFunction<T> {
  (index: number, item: T): any
}
Run Code Online (Sandbox Code Playgroud)

即使您仅使用对象来派生“跟踪依据”表达式,您的函数也必须将索引作为第一个参数。

trackByProductSKU(_index: number, product: { sku: string })
{
    // add a breakpoint or debugger statement to be 100% sure your
    // function is actually being called (!)
    debugger;
    return product.sku;
}
Run Code Online (Sandbox Code Playgroud)

2) 确保整个控件(包含 *ngFor)没有被重绘,这可能是其他东西的副作用。

  • 在循环上方添加<input/>控件*ngFor- (是 - 只是一个空文本框)
  • 加载页面并在每个文本框中输入一些内容(我通常只输入 1、2、3、4...)
  • 从列表中添加/删除项目 - 或您需要执行的任何操作来触发更改
  • 如果文本框的内容消失,则意味着您正在重新绘制整个容器控件(换句话说,您trackBy与根本问题无关)。
  • <input/>如果有多个嵌套循环,则可以放在每个“级别”。只需在每个框中键入一个值,然后查看在执行导致问题的任何操作时保留哪些值。

3) 确保trackBy函数为每一行返回唯一的值:

<li *ngFor="let item of lineItems; trackBy: trackByProductSKU">

    <input />
    Tracking value: [{{ trackByProductSKU(-1, item) }}]
</li>
Run Code Online (Sandbox Code Playgroud)

像这样在循环内按值显示曲目。这将消除任何愚蠢的错误 - 例如按属性获取不正确的轨道名称或大小写。空input元素是故意的

如果一切正常,您应该能够在每个输入框中键入内容,触发列表中的更改,并且它不应该丢失您键入的值。

4) 如果无法确定唯一值,只需返回项目本身。trackByIdentity如果您未指定 trackBy 函数,这是默认行为(来自)。

// angular/core/src/change_detection/differs/default_iterable_differ.ts

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

export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChanges<V> {

 constructor(trackByFn?: TrackByFunction<V>) {
    this._trackByFn = trackByFn || trackByIdentity;
 }
Run Code Online (Sandbox Code Playgroud)

5) 不要意外返回 null 或 undefined!

假设您正在使用product: { sku: string }trackBy 函数,并且出于某种原因,产品不再具有该属性集。(也许它变成了SKU或者有一个额外的级别。)

如果您product.sku从函数返回并且它为空,那么您将得到一些意外的行为。


Con*_*Fan 6

trackBy函数确定何时ngFor应该重新渲染循环创建的 div 元素(由 DOM 中的新元素替换)。请注意,Angular 始终可以通过修改元素的属性或属性来更新元素的变化检测。更新元素并不意味着用新元素替换它。这就是为什么将文本设置为大写会反映在浏览器中,即使 div 元素没有重新渲染。

默认情况下,在不指定trackBy函数的情况下,当相应的 item 值发生变化时,将重新渲染 div 元素。在目前的情况下,这将是当data数组项被不同的对象替换时(项“值”是对象引用);例如在执行以下方法后:

recreateDataArray() {
  this.data = this.data.map(x => Object.assign({}, x));
}
Run Code Online (Sandbox Code Playgroud)

现在,使用trackBy返回数据 item的函数id,您告诉ngFor循环id在相应项目的属性更改时重新呈现 div 元素。因此,执行上述recreateDataArray方法后,现有的 div 元素将保留在 DOM 中,但在运行以下方法后,它们将被新元素替换:

incrementIds() {
  this.data.forEach(x => { x.id += 10; });
}
Run Code Online (Sandbox Code Playgroud)

你可以试验这个 stackblitz。复选框允许打开/关闭trackByIds逻辑,控制台消息指示何时重新渲染 div 元素。“设置红色文本”按钮直接改变DOM元素的样式;您知道红色 div 元素在其内容变为黑色时已重新呈现。


Sat*_*112 0

trackBy 实际上是用来防止 DOM 中相同元素一次又一次地重新渲染。它不能像你正在使用的那样使用。让我详细说明一下。如果你有这样的数组:

data = [
{ id: 1, text: 'one' },
{ id: 2, text: 'two' },
{ id: 3, text: 'three' },


];
Run Code Online (Sandbox Code Playgroud)

单击按钮后,您可以更改数组,如下所示:

  changeArray() {


this.data = [
    { id: 1, text: 'one' },
    { id: 2, text: 'two' },
    { id: 3, text: 'three' },
    { id: 4, text: 'four' },
    { id: 5, text: 'five' },
  ];
  }

trackByIds (index: number, item: any) {
return item.id; 
  };
}
Run Code Online (Sandbox Code Playgroud)

和:

<div *ngFor="let d of data; trackBy: trackByIds">
  {{ d.text }}
</div>
<button (click)="changeArray()>Change Array</button>
Run Code Online (Sandbox Code Playgroud)

当您点击按钮调用changeArray()时。然后,trackBy 保证只有具有新 id 的项目才会被添加到 DOM,并且之前的项目不会被重新渲染,即具有 id 1-3 的项目不会在 DOM 中再次渲染。如果您认为可以使用它来防止操纵,那么您就错了。希望你能得到我!证明:点击按钮之前 单击changeArray之前 单击按钮后,仅突出显示的内容发生变化

单击按钮后,仅突出显示四个和五个,因为它们只是更改

如果使用单击按钮将 1 转换为 1 证明 1 是否转换为 ONE