Cum să îmbunătățiți performanța aplicației dvs. Angular
Publicat: 2020-04-10Când vorbim despre cele mai bune cadre frontend, este imposibil să nu menționăm Angular. Totuși, necesită mult efort din partea programatorilor pentru a-l învăța și pentru a-l folosi cu înțelepciune. Din păcate, există riscul ca dezvoltatorii care nu au experiență în Angular să poată folosi unele dintre funcțiile sale într-un mod ineficient.
Unul dintre multele lucruri la care trebuie să lucrați întotdeauna ca dezvoltator frontend este performanța aplicației. O mare parte din proiectele mele anterioare s-au concentrat pe aplicații mari, pentru întreprinderi, care continuă să fie extinse și dezvoltate. Framework-urile front-end ar fi extrem de utile aici, dar este important să le folosiți corect și rezonabil.
Am pregătit o listă rapidă cu cele mai populare strategii de creștere a performanței și sfaturi care vă pot ajuta să creșteți instantaneu performanța aplicației dvs. Angular . Vă rugăm să rețineți că toate sugestiile de aici se aplică pentru Angular în versiunea 8.
ChangeDetectionStrategy și ChangeDetectorRef
Change Detection (CD) este mecanismul Angular pentru detectarea modificărilor datelor și reacția automată la acestea. Putem enumera tipurile de bază ale modificărilor standard ale stării aplicației:
- Evenimente
- Solicitare HTTP
- Cronometre
Acestea sunt interacțiuni asincrone. Întrebarea este: de unde ar ști Angular că au avut loc unele interacțiuni (cum ar fi clic, interval, cerere http) și că este nevoie de a actualiza starea aplicației?
Răspunsul este ngZone , care este practic un sistem complex menit să urmărească interacțiunile asincrone. Dacă toate operațiunile sunt înregistrate de ngZone, Angular știe când să reacționeze la unele modificări. Dar nu știe ce s-a schimbat exact și lansează mecanismul de detectare a schimbărilor , care verifică toate componentele în primă ordine.
Fiecare componentă din aplicația Angular are propriul detector de schimbări, care definește modul în care această componentă ar trebui să acționeze atunci când a fost lansată Detectarea schimbărilor – de exemplu, dacă este nevoie să re-rendați DOM-ul unei componente (care este mai degrabă o operațiune costisitoare). Când Angular lansează Change Detection, fiecare componentă va fi verificată și vizualizarea sa (DOM) poate fi redată din nou în mod implicit.
Putem evita acest lucru folosind ChangeDetectionStrategy.OnPush:
@component({ selector: „foobar”, templateUrl: „./foobar.component.html”, styleUrls: ['./foobar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush })
După cum puteți vedea în exemplul de cod de mai sus, trebuie să adăugăm un parametru suplimentar decoratorului componentei. Dar cum funcționează cu adevărat această nouă strategie de detectare a schimbărilor?
Strategia îi spune lui Angular că o anumită componentă depinde doar de @Inputs(). De asemenea, toată componenta @Inputs() va acționa ca un obiect imuabil (de exemplu, când modificăm doar proprietatea din @Input() a unui obiect, fără a schimba referința, această componentă nu va fi verificată). Înseamnă că multe verificări inutile vor fi omise și ar trebui să mărească performanța aplicației noastre.
O componentă cu ChangeDetectionStrategy.OnPush va fi verificată numai în următoarele cazuri:
- Referința @Input() se va schimba
- Un eveniment va fi declanșat în șablonul componentei sau unul dintre copii ai acesteia
- Observabil în componentă va declanșa un eveniment
- CD-ul va fi rulat manual folosind serviciul ChangeDetectorRef
- conducta asincronă este utilizată în vizualizare (conducta asincronă marchează componenta care trebuie verificată pentru modificări – când fluxul sursă va emite o nouă valoare, această componentă va fi verificată)
Dacă nu se întâmplă nimic din cele de mai sus, utilizarea ChangeDetectionStrategy.OnPush în interiorul unei anumite componente face ca componenta și toate componentele imbricate să nu fie verificate după lansarea CD-ului.
Din fericire, putem avea în continuare control deplin asupra reacției la modificările datelor utilizând serviciul ChangeDetectorRef. Trebuie să ne amintim că, cu ChangeDetectionStrategy.OnPush în interiorul timeout-urilor, solicitărilor, apelurilor pentru abonamente, trebuie să declanșăm CD-ul manual dacă avem într-adevăr nevoie de asta:
contor = 0; constructor(private changeDetectorRef: ChangeDetectorRef) {} ngOnInit() { setTimeout(() => { aceasta.contor += 1000; this.changeDetectorRef.detectChanges(); }, 1000); }
După cum putem vedea mai sus, apelând this.changeDetectorRef.detectChanges() în cadrul funcției noastre de timeout , putem forța CD -ul manual. Dacă contorul este utilizat în interiorul șablonului în vreun fel, valoarea acestuia va fi reîmprospătată.
Ultimul sfat din această secțiune este despre dezactivarea permanentă a CD-ului pentru anumite componente. Dacă avem o componentă statică și suntem siguri că starea acesteia nu trebuie schimbată, putem dezactiva permanent CD-ul :
this.changeDetectorRef.detach()
Acest cod ar trebui să fie executat în cadrul metodei ciclului de viață ngAfterViewInit() sau ngAfterViewChecked(), pentru a ne asigura că vizualizarea noastră a fost redată corect înainte de a dezactiva reîmprospătarea datelor. Această componentă nu va mai fi verificată în timpul CD -ului, cu excepția cazului în care declanșăm manual detectChanges().
Apeluri de funcție și getters în șablon
Utilizarea apelurilor de funcții în interiorul șabloanelor execută această funcție de fiecare dată când detectorul de modificări rulează. Aceeași situație se întâmplă cu getters . Dacă este posibil, ar trebui să încercăm să evităm acest lucru. În cele mai multe cazuri, nu trebuie să executăm nicio funcție în șablonul componentei în timpul fiecărei rulări de CD . În loc de asta putem folosi țevi pure.
Țevi pure
Țevile pure sunt un fel de țevi cu o ieșire care depinde doar de intrarea sa, fără efecte secundare. Din fericire, toate conductele din Angular sunt pure în mod implicit.
@Pipe({ nume: „majuscule”, pur: adevărat })
Dar de ce ar trebui să evităm să folosim țevi cu pur: false? Răspunsul este Detectarea schimbărilor din nou. Conductele care nu sunt pure sunt executate la fiecare rulare de CD, ceea ce nu este necesar în majoritatea cazurilor și scade performanța aplicației noastre. Iată un exemplu de funcție pe care o putem schimba în conductă pură:
transform(valoare: șir, limită = 60, puncte de suspensie = '...') { dacă (!valoare || valoare.lungime <= limită) { valoare returnată; } const numberOfVisibleCharacters = value.substr(0, limit).lastIndexOf(' '); returnează `${value.substr(0, numberOfVisibleCharacters)}${elipse}`; }
Și haideți să vedem vederea:
<p class="description">truncare(text, 30)</p>
Codul de mai sus reprezintă funcția pură – fără efecte secundare, ieșirea depinde doar de intrări. În acest caz, putem înlocui pur și simplu această funcție cu țeavă pură :
@Pipe({ nume: „trunchie”, pur: adevărat }) clasa de export TruncatePipe implementează PipeTransform { transform(valoare: șir, limită = 60, puncte de suspensie = '...') { ... } }
Și, în sfârșit, în această vizualizare, obținem codul, care va fi executat numai atunci când textul a fost modificat, independent de Change Detection .
<p class="description">{{ text | trunchie: 30 }}</p>
Module de încărcare leneră și preîncărcare
Când aplicația dvs. are mai mult de o pagină, ar trebui să vă gândiți cu siguranță să creați module pentru fiecare parte logică a proiectului dvs., în special module de încărcare leneșă . Să luăm în considerare codul simplu de router Angular:

const rute: Rute = [ { cale: '', componentă: HomeComponent }, { calea: „foo”, loadChildren: ()=> import("./foo/foo.module").then(m => m.FooModule) }, { cale: 'bar', loadChildren: ()=> import("./bar/bar.module").then(m => m.BarModule) } ] @NgModule({ exporturi: [RouterModule], importuri: [RouterModule.forRoot(rute)] }) clasa AppRoutingModule {}
În exemplul de mai sus putem observa că fooModule cu toate activele sale va fi încărcat doar atunci când utilizatorul încearcă să intre pe o anumită rută (foo sau bar). Angular va genera, de asemenea, o bucată separată pentru acest modul . Încărcarea leneșă va reduce sarcina inițială .
Putem face o optimizare suplimentară. Să presupunem că vrem să facem modulele noastre de încărcare a aplicației în fundal. În acest caz, putem folosi preloadingStrategy. Angular are în mod implicit două tipuri de preloadingStrategy:
- Fără preîncărcare
- Preîncărcați toate modulele
În codul de mai sus este folosită implicit strategia NoPreloading. Aplicația începe să încarce un anumit modul la cererea utilizatorului (când utilizatorul dorește să vadă o anumită rută). Putem schimba acest lucru adăugând câteva configurații suplimentare la router.
@NgModule({ exporturi: [RouterModule], importuri: [RouterModule.forRoot(rute, { preloadingStrategy: PreloadAllModules }] }) clasa AppRoutingModule {}
Această configurație face ca ruta curentă să fie afișată cât mai curând posibil și după aceea aplicația va încerca să încarce celelalte module în fundal. Deștept, nu-i așa? Dar asta nu este tot. Dacă această soluție nu se potrivește nevoilor noastre, putem pur și simplu să scriem propria noastră strategie personalizată .
Să presupunem că vrem să preîncărcăm numai modulele selectate, de exemplu, BarModule. Indicăm acest lucru prin adăugarea unui câmp suplimentar pentru câmpul de date.
const rute: Rute = [ { cale: '', componentă: HomeComponent date: { preîncărcare: fals } }, { calea: „foo”, loadChildren: ()=> import("./foo/foo.module").then(m => m.FooModule), date: { preîncărcare: fals } }, { cale: 'bar', loadChildren: ()=> import("./bar/bar.module").then(m => m.BarModule), date: { preîncărcare: adevărat } } ]
Apoi trebuie să scriem funcția noastră personalizată de preîncărcare:
@Injectabil() clasa de export CustomPreloadingStrategy implementează PreloadingStrategy { preîncărcare(rută: Rută, încărcare: () => Observabil<orice>): Observabil<orice> { return route.data && route.data.preload ? load(): of(null); } }
Și setați-o ca preloadingStrategy:
@NgModule({ exporturi: [RouterModule], importuri: [RouterModule.forRoot(rute, { preloadingStrategy: CustomPreloadingStrategy }] }) clasa AppRoutingModule {}
Deocamdată vor fi preîncărcate numai rutele cu param { data: { preload: true } }. Restul rutelor se vor comporta ca și cum NoPreloading este setat.
PreloadingStrategy personalizată este @Injectable(), deci înseamnă că putem injecta unele servicii în interior dacă avem nevoie și personalizam preloadingStrategy în orice alt mod.
Cu instrumentele de dezvoltare ale unui browser, putem investiga creșterea performanței prin timp de încărcare inițial egal cu și fără o strategie de preîncărcare. De asemenea, ne putem uita la fila de rețea pentru a vedea că bucăți pentru alte rute se încarcă în fundal, în timp ce utilizatorul poate vedea pagina curentă fără întârzieri.
funcția trackBy
Putem presupune că majoritatea aplicațiilor Angular folosesc *ngFor pentru a repeta elementele listate în interiorul șablonului. Dacă lista repetată este, de asemenea, editabilă, trackBy este absolut obligatoriu.
<ul> <tr *ngFor="permiteți produsul produselor; trackBy: trackByProductId"> <td>{{ product.title }}</td> </tr> </ul> trackByProductId(index: număr, produs: produs) { return product.id; }
Folosind funcția trackBy, Angular este capabil să urmărească elementele colecțiilor care s-au schimbat (prin identificatorul dat) și să redea doar aceste elemente particulare. Când omitem trackBy, întreaga listă va fi reîncărcată, ceea ce poate fi o operațiune care necesită foarte multe resurse pe DOM.
Compilare anticipată (AOT).
În ceea ce privește documentația Angular:
„ (…) componentele și șabloanele furnizate de Angular nu pot fi înțelese direct de browser, aplicațiile Angular necesită un proces de compilare înainte de a putea rula într-un browser ”
Angular oferă cele două tipuri de compilare:
- Just-in-Time (JIT) – compilează o aplicație în browser în timpul execuției
- Ahead-of-Time (AOT) – compilează o aplicație în timpul construirii
Pentru utilizarea în dezvoltare, compilarea JIT ar trebui să acopere nevoile dezvoltatorului. Cu toate acestea, pentru construirea de producție ar trebui să folosim cu siguranță AOT . Trebuie să ne asigurăm că indicatorul aot din fișierul angular.json este setat la adevărat. Cele mai importante beneficii ale unei astfel de soluții includ randarea mai rapidă, mai puține solicitări asincrone, dimensiunea de descărcare a cadrului mai mică și securitate sporită.
rezumat
Performanța aplicației este ceva de care trebuie să aveți în vedere atât în timpul dezvoltării, cât și în perioada de întreținere a proiectului dumneavoastră. Cu toate acestea, căutarea posibilelor soluții pe cont propriu ar putea consuma atât timp, cât și efort. Verificarea acestor greșeli frecvent făcute și păstrarea lor în minte în timpul procesului de dezvoltare nu numai că vă va ajuta să îmbunătățiți performanța aplicației dvs. Angular în cel mai scurt timp, ci și să evitați erorile viitoare.
Aveți încredere în Miquido cu proiectul dvs. Angular
Contactaţi-neDoriți să dezvoltați o aplicație cu Miquido?
Te gândești să dai un impuls afacerii tale cu o aplicație Angular? Luați legătura cu noi și alegeți serviciile noastre de dezvoltare de aplicații Angular.