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

Daha iyi bir kullanıcı deneyimi için değişiklik algılamayı daha hızlı 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 (örneğin, bir kullanıcı etkileşimi veya XHR tamamlaması) aracılığıyla tetiklenebilir.

Değişiklik algılama güçlü bir araçtır, ancak çok sık çalıştırıldığında birçok hesaplamayı 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ılamasının ayrıntıları

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 departmanda (satış ve Ar-Ge) çalışanları listeler ve iki bileşenden oluşur:

  • Uygulamanın kök bileşeni olan AppComponent 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 listesini ve bölüm adını kabul eder. Kullanıcı bir çalışanı kaldırmaya veya eklemeye çalıştığında bileşen ilgili bir çıkışı tetikler. Bileşen, iş hesaplamasını uygulayan calculate yöntemini de tanımlar.

EmployeeListComponent için şablon:

<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ı yinelemek ve her biri 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 tarihinden itibaren bileşen ağacının tamamı için değişiklik algılamasını 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 şuna benzer bir sonuç görürsünüz:

Performans profili çıkarma

Listede çok sayıda çalışan varsa bu işlem tarayıcının kullanıcı arayüzü iş parçacığını engelleyebilir ve karelerde düşüşlere yol açarak kötü bir kullanıcı deneyimine yol açabilir.

Bileşen alt ağaçları atlanıyor

Kullanıcı satış EmployeeListComponent için metin girişini yazarken, Ar-Ge departmanındaki verilerin değişmediğini anlarsınız. Bu nedenle, bileşeninde değişiklik algılamanın çalıştırılması gerekmez. 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:

Bir bileşen alt ağacındaki değişiklik algılama

Orijinal uygulamaya uygulanmış olan söz konusu optimizasyonu burada bulabilirsiniz.

OnPush değişikliği algılama stratejisi hakkında daha fazla bilgiye resmi Angular dokümanlarından ulaşabilirsiniz.

Bu optimizasyonun etkisini görmek için StackBlitz'deki uygulamaya yeni bir çalışan girin.

Saf boru kullanımı

EmployeeListComponent için değişiklik algılama stratejisi artık OnPush olarak ayarlanmış olsa da, kullanıcı ilgili metin girişini yazarken Angular bir departmandaki tüm çalışanlar için sayısal değeri yeniden hesaplamaya devam eder.

Bu davranışı iyileştirmek için saf kanallardan 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, temiz ardışık düzenin, yalnızca önceki çağrısından farklı bir giriş alması halinde 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. Hesaplamayı yalın bir çizgiye taşırsanız Angular, dikey çizgi 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 durumda, bir çalışanın sayısal değeri güncellenmediği sürece Angular yeniden hesaplama yapmaz.

İş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. Angular, aksini belirtmediğiniz sürece tüm boruları saf olarak kabul eder.

Son olarak, EmployeeListComponent şablonu içinde bulunan ifadeyi 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 girdiğinde uygulama her bir çalışanın sayısal değerini 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 kaynağını görmek için Chrome Geliştirici Araçları'nı kullanarak uygulamanın profilini çıkarın.
  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.