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, değişiklik algılama mekanizmasını düzenli olarak çalıştırır. Böylece, veri modelinde yapılan değişiklikler uygulamanın görünümüne yansıtılı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 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.

Angular'ın değişiklik algılamasının ayrıntıları

Angular değişiklik algılama özelliğinin işleyiş şeklini anlamak için örnek bir uygulamaya göz atalı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 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 bir işletme hesaplamasına iletir ve sonucu ekranda görselleştirir.

Şimdi EmployeeListComponent uygulamasına 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 departman 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 politikasında bildirilen label özelliği arasındaki iki yönlü veri bağlaması 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, EmployeeListComponent öğesinin iki örneğidir. Her örneğin, departmandaki çalışanları temsil eden bir öğe listesi (E1, E2 vb.) vardır.

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şine yazarken Angular'ın her çalışanla ilişkili sayısal değerleri tekrar tekrar hesaplayarak son kontrolden bu yana değişmediğini doğruladığı 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+Üst Karakter+J" (veya Mac'te "Command+Option+J") tuşlarına basın.
  2. Performans sekmesini tıklayın.

tıklayın (Performans panelinin sol üst köşesinde) ve uygulamadaki metin kutularından birine yazmaya başlayın. 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 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 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 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 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 bu 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 karışık ardışık düzenler, girişleri kabul eder ve bir şablonda kullanılabilecek sonuçları döndürür. İkisi arasındaki fark, tam 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 borunun bağımsız değişkenlerinin 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.

İş hesaplamasını CalculatePipe adlı bir kanala nasıl taşıyacağınız aşağıda açıklanmıştır:

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

Borunun 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 yazı yazmanın ne kadar akıcı olduğunu görebilirsiniz.

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

Orijinal uygulamanın tam kanal optimizasyonunu içeren kodu burada bulabilirsiniz.

Sonuç

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

  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şikliği algılama stratejisini uygulayın.
  3. Çerçevenin, hesaplanan değerleri önbelleğe almasına izin vermek için ağır hesaplamaları yalın ardışık çizgilere taşıyın.