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

Daha iyi kullanıcı deneyimi için daha hızlı değişiklik algılama 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, 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ılırsa birçok hesaplamayı tetikleyebilir ve ana tarayıcı iş parçacığını engelleyebilir.

Bu gönderide, 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 edeceğinizi ve optimize edeceğinizi öğreneceksiniz.

Inside Angular değişiklik algılama

Angular'ın değişiklik algılama özelliğinin nasıl çalıştığını anlamak için örnek bir uygulamaya göz atalım.

Uygulamanın kodunu bu GitHub kod deposunda bulabilirsiniz.

Uygulama, bir şirketin iki farklı departmanında (satış ve Ar-Ge) çalışanları listeliyor ve iki bileşenden oluşuyor:

  • 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 EmployeeListComponent öğesinin iki ö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ın bir adı ve sayısal bir değeri vardır. Uygulama, çalışanın sayısal değerini iş hesaplamasına iletir ve sonucu ekranda görselleştirir.

Şimdi EmployeeListComponent ürününe 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, çalışan listesini ve departman adını giriş olarak kabul eder. Kullanıcı bir çalışanı kaldırmaya veya eklemeye çalıştığında bileşen, ilgili çı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 için yinelenir ve her biri için bir liste öğesi oluşturur. Ayrıca, giriş ile EmployeeListComponent içinde açıklanan label özelliği arasındaki iki yönlü veri bağlamaya ilişkin bir ngModel yönergesi içerir.

Uygulama, iki EmployeeListComponent örneğinde aşağıdaki bileşen ağacını oluşturur:

Bileşen ağacı

AppComponent, uygulamanın kök bileşenidir. Alt bileşenleri, EmployeeListComponent öğesinin iki ö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ı yazmaya başladığında Angular, AppComponent tarihinden itibaren bileşen ağacının tamamı için değişiklik algılamayı tetikler. Bu, kullanıcı metin girişini yazarken Angular'ın son kontrolden beri değişmediğini doğrulamak için her çalışanla ilişkilendirilmiş sayısal değerleri sürekli olarak yeniden hesapladığı anlamına gelir.

Bunun ne kadar yavaş olabileceğini görmek için StackBlitz'de 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 geldiğini doğrulayabilirsiniz.

  1. Geliştirici Araçları'nı açmak için "Control+Shift+J" (veya Mac'te "Command+Option+J") tuşlarına basın.
  2. Performans sekmesini tıklayın.

tıklayın. Chrome Geliştirici Araçları, topladığı tüm profil oluşturma verilerini işledikten sonra şuna benzer bir şey görürsünüz:

Performans profili oluşturma

Listede çok sayıda çalışan varsa bu işlem tarayıcının kullanıcı arayüzü iş parçacığını engelleyebilir ve çerçevelerin düşmesine, dolayısıyla 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 özelliğinin çalıştırılmasına gerek yoktur. Ar-Ge örneğinin değişiklik algılamayı tetiklemediğinden emin olmak için EmployeeListComponent öğesinin changeDetectionStrategy değerini 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 girdiğinde değişiklik algılama yalnızca ilgili departman için tetikleniyor:

Bir bileşen alt ağacında değişiklik algılaması

Orijinal uygulamaya uygulanan bu optimizasyonu burada bulabilirsiniz.

OnPush değişikliği algılama stratejisi hakkında daha fazla bilgiyi resmi Angular dokümanlarında bulabilirsiniz.

Bu optimizasyonun etkisini görmek için StackBlitz uygulamasına yeni bir çalışan girin.

Sade boru kullanma

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

Bu davranışı iyileştirmek için sadece kanaldan yararlanabilirsiniz. Hem saf hem de karışımlı ardışık düzenler, girişleri kabul eder ve şablonda kullanılabilecek sonuçları döndürür. Bu ikisi arasındaki fark, saf ardışık düzenin, yalnızca önceki çağrıdan farklı bir giriş alırsa 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örüntülenecek bir değer hesapladığını unutmayın. Hesaplamayı yalın ardışık düzene taşırsanız Angular, düz çizgi ifadesini yalnızca bağımsız değişkenleri değiştiğinde yeniden hesaplar. Çerçeve, referans kontrolü gerçekleştirerek kanal argümanlarının 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 dikey çizgiye şu şekilde taşıyabilirsiniz:

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);
  }
}

Düz çizginin transform yöntemi fibonacci işlevini çağırır. Borunun saf olduğuna dikkat edin. Siz aksini belirtmedikçe Angular tüm boruları saf olarak kabul eder.

Son olarak, EmployeeListComponent şablonu için içindeki 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 bölümle ilişkili metin girişini yazdığında uygulama, sayısal değeri çalışanlar için ayrı ayrı yeniden hesaplamaz.

Aşağıdaki uygulamada, yazmanın ne kadar daha akıcı olduğunu görebilirsiniz!

Son optimizasyonun etkisini görmek için StackBlitz'de bu örneği deneyin.

Orijinal uygulamaya ait salt ardışık düzen optimizasyonunu içeren kodu burada bulabilirsiniz.

Sonuç

Angular uygulamasında çalışma zamanı yavaşlamalarıyla karşılaştığınızda:

  1. Yavaşlamaların nereden geldiğini görmek için Chrome Geliştirici Araçları'nı kullanarak uygulamanın profilini oluşturun.
  2. Bir bileşenin alt ağaçlarını ayıklamak için OnPush değişiklik algılama stratejisini tanıtın.
  3. Çerçevenin, hesaplanan değerleri önbelleğe almasını sağlamak için ağır hesaplamaları salt ardışık düzenlere taşıyın.