在 @for 迴圈中指定的身分追蹤表達式,導致對應到_所有_項目的 DOM 被重新建立。這是非常昂貴的操作,常發生在處理不可變資料結構時。例如:
@Component({
template: `
<button (click)="toggleAllDone()">All done!</button>
<ul>
@for (todo of todos; track todo) {
<li>{{ todo.task }}</li>
}
</ul>
`,
})
export class App {
todos = [
{id: 0, task: 'understand trackBy', done: false},
{id: 1, task: 'use proper tracking expression', done: false},
];
toggleAllDone() {
this.todos = this.todos.map((todo) => ({...todo, done: true}));
}
}
在提供的範例中,切換項目的「done」狀態後,整個清單連同所有檢視(DOM 節點、Angular 指令、元件、查詢等)都會被重新建立(!)。在這裡,對 done 屬性做一次相對便宜的繫結更新就足夠了。
除了高效能代價之外,重新建立 DOM 樹也會導致 DOM 元素中的狀態遺失(例如:焦點、文字選取、在 iframe 中載入的網站等)。
修正方式
變更追蹤表達式,讓它能唯一識別集合中的項目,不受其物件身分影響。在討論的範例中,正確的追蹤表達式會使用唯一的 id 屬性(item.id):
@Component({
template: `
<button (click)="toggleAllDone()">All done!</button>
<ul>
@for (todo of todos; track todo.id) {
<li>{{ todo.task }}</li>
}
</ul>
`,
})
export class App {
todos = [
{id: 0, task: 'understand trackBy', done: false},
{id: 1, task: 'use proper tracking expression', done: false},
];
toggleAllDone() {
this.todos = this.todos.map((todo) => ({...todo, done: true}));
}
}