Angular'ın değişiklik algılamasını optimize etme

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 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.

Örnek uygulama

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:

Bileşen ağacı

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.

  1. Geliştirici Araçları'nı açmak için "Kontrol+Üst Karakter+J" (veya Mac'te "Komut+Option+J") tuşlarına basın.
  2. 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:

Performans profilleme

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:

Bileşen alt ağacında değişiklik algılama

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 yalnızca önceki çağrısından farklı bir giriş aldığında sonucunu 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 tek tek 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:

  1. Yavaşlamaların nereden kaynaklandığını görmek için Chrome Geliştirici Araçları ile uygulamanın profilini oluşturun.
  2. Bir bileşenin alt ağaçlarını budamak için OnPush değişiklik algılama stratejisini tanıtın.
  3. Çerçevenin hesaplanan değerleri önbelleğe almasına izin vermek için ağır hesaplamaları saf borulara taşıyın.