【问题标题】:angular ngFor trackBy does not work as I expectedangular ngFor trackBy 无法按预期工作
【发布时间】:2019-06-19 23:26:49
【问题描述】:

当我阅读 ngFortrackBy 的文档 (https://angular.io/api/common/NgForOf) 时,我想我明白 Angular 只会在 trackBy 函数返回的值发生变化时重做 DOM,但是当我使用它时在这里(https://stackblitz.com/edit/angular-playground-bveczb),我发现我其实一点也不明白。这是我的代码的基本部分:

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

还有:

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

我期望单击按钮应该将列表从小写更改为大写,但确实如此。我以为我在*ngFor 中对trackBy 使用了trackByIds 函数,并且由于trackByIds 只检查项目的id 属性,所以除了id 之外的任何内容的更改都不应该导致DOM要重做。我想我的理解是错误的。

【问题讨论】:

  • 多次重读文档链接后,我认为 trackby 是“我应该在 DOM 中添加/删除/重新排序节点吗?”这个问题的答案。当数据发生更改时,我应该反映节点数据的更改。
  • tbh,在实践中,trackBy 与 ngFor 中组件的生命周期挂钩最相关。 trackby 函数将确定何时重新运行 init 系列挂钩,但更改检测仍始终根据选定的策略运行。渲染!= 变化检测。

标签: angular


【解决方案1】:

如果trackBy 似乎不起作用:

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

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

interface TrackByFunction<T> {
  (index: number, item: T): any
}

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

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

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

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

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

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

    <input />
    Tracking value: [{{ trackByProductSKU(-1, item) }}]
</li>

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

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

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

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

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

假设您使用 product: { sku: string } 作为您的 trackBy 函数,并且无论出于何种原因,产品不再具有该属性集。 (可能改成SKU或者多了一级。)

如果你从你的函数中返回product.sku并且它是空的,那么你将会得到一些意想不到的行为。

【讨论】:

    【解决方案2】:

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

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

    recreateDataArray() {
      this.data = this.data.map(x => Object.assign({}, x));
    }
    

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

    incrementIds() {
      this.data.forEach(x => { x.id += 10; });
    }
    

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

    【讨论】:

      【解决方案3】:

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

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

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

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

      还有:

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

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

      如果使用单击按钮将一个转换为 ONE

      【讨论】:

      • 这并不能真正回答问题。
      • 我错误地发布了不完整的答案,对此深表歉意。您现在应该重新检查。
      • 我没有投反对票,而且我仍然认为您没有正确阅读该问题,这就是为什么对不影响按功能跟踪的数据进行的适当更改仍会导致重新渲染。跨度>
      • 如果在新数据中将文本从“one”更改为“ONE”,它会重新渲染第一项吗?
      • 是的,因为它是当前id值的变化
      猜你喜欢
      • 1970-01-01
      • 2019-03-20
      • 1970-01-01
      • 1970-01-01
      • 2018-09-16
      • 2017-07-19
      • 2018-01-17
      • 2021-06-05
      • 1970-01-01
      相关资源
      最近更新 更多