Szybsze wykrywanie zmian z myślą o wygodzie użytkowników
Angular okresowo uruchamia mechanizm wykrywania zmian, dzięki czemu zmiany w modelu danych są odzwierciedlane w widoku aplikacji. Wykrywanie zmian może być aktywowane ręcznie lub przez zdarzenie asynchroniczne (np. interakcja użytkownika lub wykonanie XHR).
Wykrywanie zmian to zaawansowane narzędzie, ale jeśli jest uruchamiane bardzo często, może wywołać wiele obliczeń i zablokować główny wątek przeglądarki.
Z tego posta dowiesz się, jak kontrolować i optymalizować mechanizm wykrywania zmian poprzez pomijanie części aplikacji i uruchamianie wykrywania zmian tylko wtedy, gdy jest to konieczne.
Wykrywanie zmian w Inside Angular
Aby zrozumieć, jak działa wykrywanie zmian w Angular, przyjrzyjmy się przykładowej aplikacji.
Kod aplikacji znajdziesz w tym repozytorium GitHub.
Aplikacja zawiera listę pracowników z dwóch działów firmy – działu sprzedaży oraz badań i rozwoju – i składa się z dwóch elementów:
AppComponent
, który jest głównym komponentem aplikacji,- 2 wystąpienia
EmployeeListComponent
, jedna dla sprzedaży, a druga dla badań i rozwoju.
W szablonie dla AppComponent
możesz zobaczyć 2 wystąpienia tekstu EmployeeListComponent
:
<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>
Każdy pracownik ma swoje imię i nazwisko oraz wartość liczbową. Aplikacja przekazuje wartość liczbową pracownika do obliczeń firmy i wizualizuje wynik na ekranie.
.Spójrz na EmployeeListComponent
:
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
akceptuje jako dane wejściowe listę pracowników i nazwę działu. Gdy użytkownik próbuje usunąć lub dodać pracownika, komponent aktywuje odpowiednie dane wyjściowe. Komponent określa też metodę calculate
, która implementuje obliczenia związane z firmą.
Oto szablon dla adresu EmployeeListComponent
:
<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>
Ten kod powtarza procedurę dla wszystkich pracowników na liście i w przypadku każdego z nich renderuje jej pozycję. Zawiera też dyrektywę ngModel
służącą do dwukierunkowego wiązania danych między danymi wejściowymi a właściwością label
zadeklarowaną w zasadzie EmployeeListComponent
.
Na podstawie 2 wystąpień EmployeeListComponent
aplikacja tworzy to drzewo komponentów:
AppComponent
to główny komponent aplikacji. Jego komponenty podrzędne to 2 instancje elementu EmployeeListComponent
. Każda instancja ma listę elementów (E1, E2 itd.), które reprezentują poszczególnych pracowników działu.
Gdy użytkownik zacznie wpisywać imię i nazwisko nowego pracownika w polu do wprowadzania danych w polu EmployeeListComponent
, Angular aktywuje wykrywanie zmian dla całego drzewa komponentów, począwszy od AppComponent
. Oznacza to, że gdy użytkownik wpisuje tekst, Angular wielokrotnie przelicza wartości liczbowe powiązane z poszczególnymi pracownikami, aby upewnić się, że nie zmieniły się od czasu ostatniego sprawdzenia.
Aby sprawdzić, jak wolno to może działać, otwórz niezoptymalizowaną wersję projektu w StackBlitz i spróbuj wpisać imię i nazwisko pracownika.
Aby sprawdzić, czy spowolnienie pochodzi z funkcji fibonacci
, skonfiguruj przykładowy projekt i otwórz kartę Wydajność w Narzędziach deweloperskich w Chrome.
- Naciśnij „Control + Shift + J” (lub „Command + Option + J” na Macu), aby otworzyć Narzędzia deweloperskie.
- Kliknij kartę Skuteczność.
Teraz kliknij Rejestruj (w lewym górnym rogu panelu Skuteczność) i zacznij pisać w jednym z pól tekstowych w aplikacji. Za kilka sekund kliknij ponownie Nagraj , aby zatrzymać nagrywanie. Gdy Narzędzia deweloperskie w Chrome przetworzą wszystkie zebrane dane profilowania, zobaczysz coś takiego:
Jeśli na liście jest wielu pracowników, ten proces może zablokować wątek interfejsu użytkownika przeglądarki i spowodować spadek liczby klatek, co negatywnie wpływa na wygodę użytkowników.
Pomijam poddrzewa komponentów
Gdy użytkownik wpisuje tekst dotyczący sprzedaży EmployeeListComponent
, wiesz, że dane w dziale badań i rozwoju się nie zmieniają – nie ma więc powodu, aby uruchamiać wykrywanie zmian w tym komponencie. Aby mieć pewność, że instancja R&D nie aktywuje wykrywania zmian, ustaw wartość changeDetectionStrategy
parametru EmployeeListComponent
na OnPush
:
import { ChangeDetectionStrategy, ... } from '@angular/core';
@Component({
selector: 'app-employee-list',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['employee-list.component.css']
})
export class EmployeeListComponent {...}
Teraz gdy użytkownik wpisze tekst, wykrywanie zmian zostanie aktywowane tylko w przypadku odpowiedniego działu:
Informacje na temat optymalizacji zastosowanej do oryginalnej aplikacji znajdziesz tutaj.
Więcej informacji o strategii wykrywania zmian w usłudze OnPush
znajdziesz w oficjalnej dokumentacji Angular.
Aby zobaczyć efekty optymalizacji, wpisz nowego pracownika w aplikacji w StackBlitz.
Korzystanie z czystych rur
Mimo że strategia wykrywania zmian w elemencie EmployeeListComponent
jest teraz ustawiona na OnPush
, Angular nadal przelicza wartości liczbowe wszystkich pracowników w danym dziale, gdy użytkownik wpisze odpowiedni tekst.
Aby poprawić to zachowanie, możesz użyć czystych rur. Zarówno czyste, jak i zanieczyszczone potoki akceptują dane wejściowe i zwracane wyniki, których można użyć w szablonie. Różnica między nimi polega na tym, że czysta kreska pionowa przeliczy wynik tylko wtedy, gdy otrzyma inne dane wejściowe z poprzedniego wywołania.
Pamiętaj, że aplikacja oblicza wartość do wyświetlenia na podstawie wartości liczbowej pracownika, wywołując metodę calculate
zdefiniowaną w zasadzie EmployeeListComponent
. Jeśli przeniesiesz obliczenia na czystą pionową kreskę, Angular ponownie obliczy wyrażenie tego typu tylko wtedy, gdy zmienią się jego argumenty. Platforma określi, czy argumenty kreski pionowej uległy zmianie, wykonując sprawdzenie odwołania. Oznacza to, że Angular nie wykona żadnych obliczeń ponownie, chyba że zaktualizujesz wartość liczbową pracownika.
Aby przenieść obliczenia biznesowe do potoku o nazwie CalculatePipe
:
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);
}
}
Metoda transform
potoku wywołuje funkcję fibonacci
. Zwróć uwagę, że kreska jest czysta. Angular uzna wszystkie rury za czyste, chyba że określisz inaczej.
Na koniec zaktualizuj wyrażenie w szablonie dla EmployeeListComponent
:
<mat-chip-list>
<md-chip>
{{ item.num | calculate }}
</md-chip>
</mat-chip-list>
Znakomicie. Teraz gdy użytkownik wpisze tekst powiązany z dowolnym działem, aplikacja nie obliczy ponownie wartości liczbowych poszczególnych pracowników.
W aplikacji poniżej możesz zobaczyć, o ile płynniejsze pisanie jest.
Aby zobaczyć efekt ostatniej optymalizacji, skorzystaj z tego przykładu w StackBlitz.
Kod z optymalizacją logiczną pierwotnej aplikacji jest dostępny tutaj.
Podsumowanie
Gdy w aplikacji Angular występują opóźnienia w czasie działania:
- Profiluj aplikację za pomocą Narzędzi deweloperskich w Chrome, aby zobaczyć, skąd się biorą spowolnienia.
- Przedstaw strategię wykrywania zmian
OnPush
, aby wyciąć poddrzewa komponentu. - Przenieś duże obliczenia do czystych potoków, aby umożliwić platformie buforowanie obliczonych wartości.