Daha iyi bir kullanıcı deneyimi için daha hızlı değişiklik algılama özelliğini uygulayın.
Angular, veri modelindeki değişikliklerin uygulamanın görünümüne yansıtılması için değişiklik algılama mekanizmasını düzenli olarak çalıştırır. Değişiklik algılama manuel olarak veya eşzamansız bir etkinlik (ör. kullanıcı etkileşimi veya XHR tamamlama) aracılığıyla tetiklenebilir.
Değişiklik algılama güçlü bir araçtır ancak çok sık çalıştırılırsa çok sayıda hesaplama tetikleyebilir ve ana tarayıcı iş parçacığını engelleyebilir.
Bu yayında, uygulamanızın bazı bölümlerini atlayarak ve değişiklik algılamayı yalnızca gerektiğinde çalıştırarak değişiklik algılama mekanizmasını nasıl kontrol edip optimize edeceğinizi öğreneceksiniz.
Angular'ın değişiklik algılama mekanizması
Angular'ın değişiklik algılamasının işleyiş şeklini anlamak için bir örnek uygulamaya bakalım.
Uygulamanın kodunu bu GitHub deposunda bulabilirsiniz.
Uygulama, bir şirketteki iki departmanın (satış ve Ar-Ge) çalışanlarını listeler ve iki bileşenden oluşur:
AppComponent
, uygulamanın kök bileşenidir ve- Biri satış, diğeri Ar-Ge için olmak üzere iki
EmployeeListComponent
örneği.
AppComponent
şablonunda iki EmployeeListComponent
örneğini görebilirsiniz:
<app-employee-list
[data]="salesList"
department="Sales"
(add)="add(salesList, $event)"
(remove)="remove(salesList, $event)"
></app-employee-list>
<app-employee-list
[data]="rndList"
department="R&D"
(add)="add(rndList, $event)"
(remove)="remove(rndList, $event)"
></app-employee-list>
Her çalışan için bir ad ve sayısal değer vardır. Uygulama, çalışanın sayısal değerini bir işletme hesaplamasına iletir ve sonucu ekranda görselleştirir.
Şimdi EmployeeListComponent
'e göz atın:
const fibonacci = (num: number): number => {
if (num === 1 || num === 2) {
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
};
@Component(...)
export class EmployeeListComponent {
@Input() data: EmployeeData[];
@Input() department: string;
@Output() remove = new EventEmitter<EmployeeData>();
@Output() add = new EventEmitter<string>();
label: string;
handleKey(event: any) {
if (event.keyCode === 13) {
this.add.emit(this.label);
this.label = '';
}
}
calculate(num: number) {
return fibonacci(num);
}
}
EmployeeListComponent
, giriş olarak çalışan listesi ve bölüm adı kabul eder. Kullanıcı bir çalışanı kaldırmaya veya eklemeye çalıştığında bileşen, ilgili bir çıkışı tetikler. Bileşen, işletme hesaplamasını uygulayan calculate
yöntemini de tanımlar.
EmployeeListComponent
için şablonu aşağıda bulabilirsiniz:
<h1 title="Department">{{ department }}</h1>
<mat-form-field>
<input placeholder="Enter name here" matInput type="text" [(ngModel)]="label" (keydown)="handleKey($event)">
</mat-form-field>
<mat-list>
<mat-list-item *ngFor="let item of data">
<h3 matLine title="Name">
{{ item.label }}
</h3>
<md-chip title="Score" class="mat-chip mat-primary mat-chip-selected" color="primary" selected="true">
{{ calculate(item.num) }}
</md-chip>
</mat-list-item>
</mat-list>
Bu kod, listedeki tüm çalışanlar için iterasyon yapar ve her çalışan için bir liste öğesi oluşturur. Ayrıca, giriş ile EmployeeListComponent
içinde tanımlanan label
mülkü arasında iki yönlü veri bağlama için bir ngModel
yönergesi de içerir.
Uygulama, iki EmployeeListComponent
örneğiyle aşağıdaki bileşen ağacını oluşturur:
AppComponent
, uygulamanın kök bileşenidir. Alt bileşenleri, iki EmployeeListComponent
örneğidir. Her örnekte, departmandaki çalışanları temsil eden bir öğe listesi (E1, E2 vb.) bulunur.
Kullanıcı, EmployeeListComponent
içindeki giriş kutusuna yeni bir çalışanın adını girmeye başladığında Angular, AppComponent
'ten itibaren tüm bileşen ağacı için değişiklik algılamayı tetikler. Bu, kullanıcı metin girişini yazarken Angular'ın, her çalışanla ilişkili sayısal değerleri son kontrolden bu yana değişmediğinden emin olmak için tekrar tekrar yeniden hesapladığı anlamına gelir.
Bunun ne kadar yavaş olabileceğini görmek için StackBlitz'teki projenin optimize edilmemiş sürümünü açın ve bir çalışan adı girmeyi deneyin.
Örnek projeyi oluşturup Chrome Geliştirici Araçları'nın Performans sekmesini açarak yavaşlamanın fibonacci
işlevinden kaynaklandığını doğrulayabilirsiniz.
- Geliştirici Araçları'nı açmak için "Kontrol+Üst Karakter+J" (veya Mac'te "Komut+Option+J") tuşlarına basın.
- Performans sekmesini tıklayın.
Ardından Performans panelinin sol üst köşesindeki Kaydet'i tıklayın ve uygulamadaki metin kutularından birine yazmaya başlayın. Kaydı durdurmak için birkaç saniye içinde Kaydet'i tekrar tıklayın. Chrome Geliştirici Araçları, topladığı tüm profil oluşturma verilerini işledikten sonra aşağıdakine benzer bir ekran görürsünüz:
Listede çok sayıda çalışan varsa bu işlem, tarayıcının kullanıcı arayüzü ileti dizisini engelleyebilir ve kare sayısının düşmesine neden olarak kötü bir kullanıcı deneyimine yol açabilir.
Bileşen alt ağaçlarını atlama
Kullanıcı satış EmployeeListComponent
için metin girişini yazarken Ar-Ge departmanındaki verilerin değişmediğini bilirsiniz. Bu nedenle, bileşeninde değişiklik algılama işlemini çalıştırmanın bir anlamı yoktur. Ar-Ge örneğinin değişiklik algılamayı tetiklemediğinden emin olmak için EmployeeListComponent
öğesinin changeDetectionStrategy
özelliğini OnPush
olarak ayarlayın:
import { ChangeDetectionStrategy, ... } from '@angular/core';
@Component({
selector: 'app-employee-list',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['employee-list.component.css']
})
export class EmployeeListComponent {...}
Artık kullanıcı bir metin girişi yazdığında değişiklik algılama yalnızca ilgili departman için tetiklenir:
Bu optimizasyonun orijinal uygulamaya uygulanmış halini burada bulabilirsiniz.
OnPush
değişiklik algılama stratejisi hakkında daha fazla bilgiyi resmi Angular dokümanlarında bulabilirsiniz.
Bu optimizasyonun etkisini görmek için StackBlitz'teki uygulamaya yeni bir çalışan girin.
Saf boruları kullanma
EmployeeListComponent
için değişiklik algılama stratejisi artık OnPush
olarak ayarlanmış olsa da Angular, kullanıcı ilgili metin girişini yazdığında bir departmandaki tüm çalışanların sayısal değerini yeniden hesaplamaya devam eder.
Bu davranışı iyileştirmek için saf borulardan yararlanabilirsiniz. Hem saf hem de saf olmayan borular girişleri kabul eder ve şablonda kullanılabilecek sonuçlar döndürür. İkisi arasındaki fark, saf boruların sonucunu yalnızca önceki çağrısından farklı bir giriş aldığında yeniden hesaplamasıdır.
Uygulamanın, EmployeeListComponent
içinde tanımlanan calculate
yöntemini çağırarak çalışanın sayısal değerine göre gösterilecek bir değer hesapladığını unutmayın. Hesabı saf bir boruya taşırsanız Angular, boru ifadesini yalnızca bağımsız değişkenleri değiştiğinde yeniden hesaplar. Çerçeve, referans kontrolü gerçekleştirerek borudaki bağımsız değişkenlerin değişip değişmediğini belirler. Bu, bir çalışanın sayısal değeri güncellenmediği sürece Angular'ın yeniden hesaplama yapmayacağı anlamına gelir.
İşletme hesaplamasını CalculatePipe
adlı bir boruya taşımak için:
import { Pipe, PipeTransform } from '@angular/core';
const fibonacci = (num: number): number => {
if (num === 1 || num === 2) {
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
};
@Pipe({
name: 'calculate'
})
export class CalculatePipe implements PipeTransform {
transform(val: number) {
return fibonacci(val);
}
}
Borudaki transform
yöntemi fibonacci
işlevini çağırır. Borunun saf olduğuna dikkat edin. Aksi belirtilmediği sürece Angular tüm boruları saf olarak kabul eder.
Son olarak, şablondaki ifadeyi EmployeeListComponent
için güncelleyin:
<mat-chip-list>
<md-chip>
{{ item.num | calculate }}
</md-chip>
</mat-chip-list>
İşte bu kadar. Artık kullanıcı herhangi bir departmanla ilişkili metin girişini yazdığında uygulama, çalışanlar için sayısal değeri yeniden hesaplamaz.
Aşağıdaki uygulamada, yazmanın ne kadar akıcı olduğunu görebilirsiniz.
Son optimizasyonun etkisini görmek için bu örneği StackBlitz'te deneyin.
Orijinal uygulamanın saf boru optimizasyonuna sahip kodunu burada bulabilirsiniz.
Sonuç
Angular uygulamasında çalışma zamanında yavaşlamayla karşılaşırsanız:
- Yavaşlamaların nereden kaynaklandığını görmek için Chrome Geliştirici Araçları ile uygulamanın profilini oluşturun.
- Bir bileşenin alt ağaçlarını budamak için
OnPush
değişiklik algılama stratejisini tanıtın. - Çerçevenin hesaplanan değerleri önbelleğe almasına izin vermek için ağır hesaplamaları saf borulara taşıyın.