@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 트리를 다시 생성하면 성능이 크게 저하될 뿐 아니라 포커스, 텍스트 선택, iframe에 로드한 사이트 같은 DOM 엘리먼트의 상태도 사라집니다.
오류 해결
객체의 동일성과 관계없이 컬렉션의 항목을 고유하게 식별하도록 추적 표현식을 변경하세요. 위 예제에서는 고유한 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}));
}
}