在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,手机号,身份证号之类的唯一值。