0 Tage Serie
javascript / expert
Snippet

Strukturelle Gleichheitswächter für OnPush Change Detection mit Array-Schnappschüssen

OnPush-Komponenten rendern normalerweise bei jeder neuen Array-Referenz an einem @Input neu, selbst wenn der Inhalt identisch ist — eine häufige Quelle für verschwendete Change-Detection-Zyklen in listenlastigen UIs. Dieses Snippet installiert einen benutzerdefinierten Setter, der vor der Übernahme der neuen Referenz eine strukturelle Gleichheitsprüfung durchführt, sodass ein Elternteil, das ein Array mit denselben logischen Zeilen neu erzeugt (häufig bei immutablen State-Stores), kein Re-Rendering mehr erzwingt. Kombiniert mit trackBy bleiben sowohl der komponentenweite CD-Durchlauf als auch der DOM-Abgleich minimal.

snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
interface Row { id: string; score: number; }
function shallowRowsEqual(a: readonly Row[], b: readonly Row[]): boolean {
if (a === b) return true;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i].id !== b[i].id || a[i].score !== b[i].score) return false;
}
return true;
}
@Component({
selector: 'app-leaderboard',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div *ngFor="let row of rows; trackBy: trackById">{{ row.id }}: {{ row.score }}</div>`,
})
class LeaderboardComponent {
private _rows: readonly Row[] = [];
@Input() set rows(next: readonly Row[]) {
if (!shallowRowsEqual(this._rows, next)) {
this._rows = next;
}
}
get rows(): readonly Row[] {
return this._rows;
}
trackById(_index: number, row: Row): string {
return row.id;
}
}
angular
Erklärung
1
function shallowRowsEqual(a: readonly Row[], b: readonly Row[]): boolean {
readonly-Parameter dokumentieren und erzwingen, dass dieser Vergleich keines der Arrays mutiert.
2
if (a === b) return true;
Ein schneller Referenzgleichheits-Shortcut vermeidet die O(n)-Schleife im häufigen Fall eines No-op-Updates.
3
@Input() set rows(next: readonly Row[]) {
Ein Setter statt eines einfachen Felds fängt jedes eingehende Binding ab, um den Gleichheitswächter anzuwenden.
4
if (!shallowRowsEqual(this._rows, next)) {
Nur bei echter Änderung wird _rows neu zugewiesen, sodass OnPush sonst eine stabile Referenz sieht und das Rendern überspringt.
5
trackById(_index: number, row: Row): string {
trackBy verhindert vollständigen DOM-Knoten-Abbau selbst in den seltenen Fällen, in denen sich die Array-Referenz doch ändert.