Angular性能优化之——trackBy

在angular里,你想在模板中遍历一个数组时,你需要用到ngFor

比如这样

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'},
  ]
}

这个时候如果遍历的数组数据发生了变化,angular就会把跟数据关联的所有DOM元素删除掉在重新生成,这样的DOM操作很消耗性能。(注意:数据变化指的是用新数组代替旧数组,数组的引用发生了变化)

为什么angular会这样做,这是因为angular无法跟踪数组中的项目,不知道删除或添加了哪些项目,所以就全部删除在生成。

比如我们往数组里添加一个项目

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'},
    ]
  }
}

 

点击添加看一下DOM的生成情况

可以看到整个ul所有的li都重新生成了

为了避免这种情况,angular提供了trackBy函数,来告诉angular该怎么跟踪数据里的项目。trackBy函数接收两个参数,一个是当前项的index,一个就是当前项本身,并返回一个唯一的标识,比如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;
  }
}

这时再来添加一个项目,看一下DOM的生成情况

可以看到只是新生成了一个li,并没有全部重新删除在生成,这样节省了性能。

接下来有一个地方要注意一下,就是trackByFn返回的标识符最好是id之类的,而不是index,因为index虽然具有唯一性,但它并没有强制的绑定关系,没有标识性,随时都有可能变化,这样就有可能产生bug。来试验一下,trackBy返回index,然后往数据的头部添加一个项目

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

 

在输入框输入姓名后点击addList2

bug产生了,输入框的内容不对。而我们在trackBy里返回id看看

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

回到页面输入姓名

点击addList2

可以看到这回输入框的内容对了。

为什么会这样,因为angular在生成真实的DOM的时候,会先生成一个虚拟DOM,你数据发生改变了,angular会根据新数据生成一个新的虚拟DOM,然后跟之前那个旧虚拟DOM进行对比,怎么对比,就是根据你trackBy返回的哪个标识符来对比,标识符找到对应的DOM,对比一下里面的节点,节点不对就重新生成,节点一样就把原来的那个真实DOM里的节点拿过来用。这种情况下如果你的节点是个input之类的输入型节点,就会把之前输入的内容也带过来,就会产生上面的bug, 因为index已经变了, 并不能找到对应的虚拟DOM。

所以index最好不要作为唯一标识符,尽量使用id,手机号,身份证号之类的唯一值。

猜你喜欢

转载自blog.csdn.net/KenkoTech/article/details/128034684