Angular performance optimization - trackBy

In angular, when you want to iterate over an array in a template, you need to use ngFor

like this

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div>人员列表</div>
    <ul>
      <li *ngFor="let item of list;">
        <span>name: {
   
   {item.name}}</span>---<span>age: {
   
   {item.age}}</span>--<input type="text">
      </li>
    </ul>
  `
})
export class AppComponent {
  list = [
    { name: '张三', age: 18, id: '001'},
    { name: '李四', age: 19, id: '002'},
    { name: '王五', age: 20, id: '003'},
  ]
}

At this time, if the traversed array data changes, Angular will delete all DOM elements associated with the data and regenerate them. Such DOM operations consume performance. ( Note: data change refers to replacing the old array with a new array, and the reference of the array has changed )

Why does angular do this? This is because angular cannot track the items in the array, and does not know which items have been deleted or added, so they are all deleted and generated.

For example, we add an item to the array

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div>人员列表</div>
    <button (click)="addList()">addList</button>
    <ul>
      <li *ngFor="let item of list;">
        <span>name: {
   
   {item.name}}</span>---<span>age: {
   
   {item.age}}</span>--<input type="text">
      </li>
    </ul>
  `
})
export class AppComponent {
  list = [
    { name: '张三', age: 18, id: '001'},
    { name: '李四', age: 19, id: '002'},
    { name: '王五', age: 20, id: '003'},
  ]

  addList() {
    this.list = [
      { name: '张三', age: 18, id: '001'},
      { name: '李四', age: 19, id: '002'},
      { name: '王五', age: 20, id: '003'},
      { name: '老刘', age: 30, id: '004'},
    ]
  }
}

 

Click Add to see the DOM generation

You can see that all lis in the entire ul are regenerated

In order to avoid this situation, angular provides the trackBy function to tell angular how to track the items in the data. The trackBy function receives two parameters, one is the index of the current item, and the other is the current item itself, and returns a unique identifier, such as id

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div>人员列表</div>
    <button (click)="addList()">addList</button>
    <ul>
      <li *ngFor="let item of list; trackBy: trackByFn;">
        <span>name: {
   
   {item.name}}</span>---<span>age: {
   
   {item.age}}</span>--<input type="text">
      </li>
    </ul>
  `
})
export class AppComponent {
  list = [
    { name: '张三', age: 18, id: '001'},
    { name: '李四', age: 19, id: '002'},
    { name: '王五', age: 20, id: '003'},
  ]

  addList() {
    this.list = [
      { name: '张三', age: 18, id: '001'},
      { name: '李四', age: 19, id: '002'},
      { name: '王五', age: 20, id: '003'},
      { name: '老刘', age: 30, id: '004'},
    ]
  }

  trackByFn(index: number, item: any) {
    return item.id;
  }
}

At this time, add another item to see the generation of DOM

It can be seen that only a new li is generated, and all of them are not deleted and generated, which saves performance.

The next thing to pay attention to is that the identifier returned by trackByFn should preferably be id or the like, not index, because although index is unique, it has no mandatory binding relationship, no identification, and can be used at any time. There may be changes, so there may be bugs. Let's try it out, trackBy returns index, and then adds an item to the head of the data

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div>人员列表</div>
    <button (click)="addList()">addList</button>
    <button (click)="addList2()">addList2</button>
    <ul>
      <li *ngFor="let item of list; trackBy: trackByFn;">
        <span>name: {
   
   {item.name}}</span>---<span>age: {
   
   {item.age}}</span>--<input type="text">
      </li>
    </ul>
  `
})
export class AppComponent {
  list = [
    { name: '张三', age: 18, id: '001'},
    { name: '李四', age: 19, id: '002'},
    { name: '王五', age: 20, id: '003'},
  ]

  addList() {
    this.list = [
      { name: '张三', age: 18, id: '001'},
      { name: '李四', age: 19, id: '002'},
      { name: '王五', age: 20, id: '003'},
      { name: '老刘', age: 30, id: '004'},
    ]
  }

  addList2() {
    this.list = [
      { name: '老刘', age: 30, id: '004'},
      { name: '张三', age: 18, id: '001'},
      { name: '李四', age: 19, id: '002'},
      { name: '王五', age: 20, id: '003'},
    ]
  }

  trackByFn(index: number, item: any) {
    return index;
  }
}

 

After entering the name in the input box, click addList2

A bug has occurred, and the content of the input box is wrong. And we return id in trackBy to see

trackByFn(index: number, item: any) {
    return item.id;
  }

Go back to the page and enter your name

Click addList2

You can see that the content of the input box is correct this time.

Why is this so, because when angular generates a real DOM, it will first generate a virtual DOM. When your data changes, angular will generate a new virtual DOM based on the new data, and then compare it with the old virtual DOM before. How? The comparison is to compare according to the identifier returned by your trackBy. The identifier finds the corresponding DOM, and compares the nodes inside. In this case, if your node is an input node such as input, the previously input content will be brought over, and the above bug will be generated, because the index has changed, and the corresponding virtual DOM cannot be found.

Therefore, it is best not to use index as a unique identifier, and try to use unique values ​​such as id, mobile phone number, and ID number.

Guess you like

Origin blog.csdn.net/KenkoTech/article/details/128034684