Wydajne komponenty React: przewodnik po optymalizacji wydajności React

Opublikowany: 2022-03-11

Od momentu wprowadzenia React zmienił sposób, w jaki programiści front-end myślą o tworzeniu aplikacji internetowych. Dzięki wirtualnemu DOM, React sprawia, że ​​aktualizacje interfejsu użytkownika są tak wydajne, jak to tylko możliwe, dzięki czemu Twoja aplikacja internetowa jest szybsza. Ale dlaczego średniej wielkości aplikacje internetowe React nadal mają słabą wydajność?

Cóż, wskazówka polega na tym, jak korzystasz z Reacta.

Nowoczesna biblioteka front-end, taka jak React, nie przyspiesza w magiczny sposób Twojej aplikacji. Wymaga od programisty zrozumienia, jak działa React i jak komponenty przechodzą przez różne fazy cyklu życia komponentu.

Dzięki React możesz uzyskać wiele ulepszeń wydajności, które ma do zaoferowania, mierząc i optymalizując sposób i czas renderowania komponentów. A React zapewnia tylko narzędzia i funkcje niezbędne, aby to ułatwić.

Przyspiesz swoją aplikację React, optymalizując proces renderowania i różnicowania komponentów.

W tym samouczku React dowiesz się, jak mierzyć wydajność swoich komponentów React i optymalizować je, aby zbudować znacznie wydajniejszą aplikację internetową React. Dowiesz się również, w jaki sposób kilka najlepszych praktyk JavaScript pomaga sprawić, by Twoja aplikacja internetowa React zapewniała znacznie płynniejsze wrażenia użytkownika.

Jak działa reakcja?

Zanim zagłębimy się w techniki optymalizacji, musimy lepiej zrozumieć, jak działa React.

Podstawą rozwoju Reacta jest prosta i oczywista składnia JSX oraz zdolność React do budowania i porównywania wirtualnych DOM-ów. Od czasu wydania React wpłynął na wiele innych bibliotek front-end. Biblioteki takie jak Vue.js również opierają się na idei wirtualnych DOM-ów.

Oto jak działa React:

Każda aplikacja React zaczyna się od komponentu głównego i składa się z wielu komponentów tworzących drzewo. Komponenty w React to „funkcje”, które renderują interfejs użytkownika na podstawie danych (props i stanu), które otrzymuje.

Możemy to symbolizować jako F .

 UI = F(data)

Użytkownicy wchodzą w interakcję z interfejsem użytkownika i powodują zmianę danych. Niezależnie od tego, czy interakcja obejmuje kliknięcie przycisku, dotknięcie obrazu, przeciąganie elementów listy, żądania AJAX wywołujące interfejsy API itp., wszystkie te interakcje zmieniają tylko dane. Nigdy nie powodują bezpośredniej zmiany interfejsu użytkownika.

Tutaj dane to wszystko, co określa stan aplikacji internetowej, a nie tylko to, co zapisałeś w swojej bazie danych. Nawet bity stanów frontonu (np. która karta jest aktualnie wybrana lub czy pole wyboru jest aktualnie zaznaczone) są częścią tych danych.

Za każdym razem, gdy nastąpi zmiana w tych danych, React używa funkcji komponentu do ponownego renderowania interfejsu użytkownika, ale tylko wirtualnie:

 UI1 = F(data1) UI2 = F(data2)

React oblicza różnice między obecnym i nowym interfejsem użytkownika, stosując algorytm porównania w dwóch wersjach swojego wirtualnego DOM.

 Changes = Diff(UI1, UI2)

React następnie kontynuuje stosowanie tylko zmian interfejsu użytkownika do rzeczywistego interfejsu użytkownika w przeglądarce.

Gdy dane powiązane z komponentem ulegną zmianie, React określa, czy wymagana jest rzeczywista aktualizacja DOM. Dzięki temu React może uniknąć potencjalnie kosztownych operacji manipulacji DOM w przeglądarce, takich jak tworzenie węzłów DOM i uzyskiwanie dostępu do istniejących poza koniecznością.

To powtarzające się porównywanie i renderowanie komponentów może być jednym z głównych źródeł problemów z wydajnością Reacta w dowolnej aplikacji React. Tworzenie aplikacji React, w której algorytm porównywania nie jest w stanie skutecznie się pogodzić, powodując, że cała aplikacja jest renderowana wielokrotnie, może skutkować frustrująco wolnym działaniem.

Od czego zacząć optymalizację?

Ale co dokładnie optymalizujemy?

Widzisz, podczas początkowego procesu renderowania React buduje drzewo DOM w następujący sposób:

Wirtualny DOM komponentów React

Biorąc pod uwagę część zmian danych, chcemy, aby React ponownie wyrenderował tylko te komponenty, na które zmiana ma bezpośredni wpływ (i być może nawet pominął proces porównywania dla pozostałych komponentów):

Reaguj renderując optymalną liczbę komponentów

Jednak to, co ostatecznie robi React, to:

Reaguj marnując zasoby, renderując wszystkie komponenty

Na powyższym obrazku wszystkie żółte węzły są renderowane i różnicowane, co skutkuje marnowaniem czasu/zasobów obliczeniowych. W tym miejscu skupimy się przede wszystkim na optymalizacji. Skonfigurowanie każdego komponentu do renderowania różnic tylko wtedy, gdy jest to konieczne, pozwoli nam odzyskać te zmarnowane cykle procesora.

Twórcy biblioteki React wzięli to pod uwagę i udostępnili nam do tego haczyk: funkcję, która pozwala nam powiedzieć Reactowi, kiedy można pominąć renderowanie komponentu.

Najpierw pomiar

Jak Rob Pike ujmuje to dość elegancko jako jedną ze swoich zasad programowania:

Mierzyć. Nie dostrajaj prędkości, dopóki nie zmierzysz, a nawet wtedy nie rób tego, chyba że jedna część kodu przytłacza resztę.

Nie rozpoczynaj optymalizacji kodu, który Twoim zdaniem może spowalniać Twoją aplikację. Zamiast tego pozwól, by narzędzia do pomiaru wydajności React poprowadziły Cię przez tę drogę.

React ma do tego potężne narzędzie. Korzystając z biblioteki react-addons-perf , możesz uzyskać przegląd ogólnej wydajności swojej aplikacji.

Użycie jest bardzo proste:

 Import Perf from 'react-addons-perf' Perf.start(); // use the app Perf.stop(); Perf.printWasted();

Spowoduje to wydrukowanie tabeli z ilością czasu komponentów straconych na renderowanie.

Tabela komponentów marnujących czas na renderowanie

Biblioteka udostępnia inne funkcje, które pozwalają osobno wydrukować różne aspekty straconego czasu (np. za pomocą funkcji printInclusive() lub printExclusive() ), a nawet wydrukować operacje manipulacji DOM (za pomocą funkcji printOperations() ).

Krok dalej w benchmarking

Jeśli jesteś osobą wizualną, to react-perf-tool jest właśnie tym, czego potrzebujesz.

react-perf-tool jest oparty na bibliotece react-addons-perf . Zapewnia bardziej wizualny sposób debugowania wydajności aplikacji React. Wykorzystuje podstawową bibliotekę, aby uzyskać pomiary, a następnie wizualizuje je w postaci wykresów.

Wizualizacja komponentów marnujących czas na renderowanie

Najczęściej jest to znacznie wygodniejszy sposób wykrywania wąskich gardeł. Możesz go łatwo używać, dodając go jako komponent do swojej aplikacji.

Czy należy zareagować, zaktualizować komponent?

Domyślnie React uruchomi się, wyrenderuje wirtualny DOM i porówna różnice dla każdego komponentu w drzewie pod kątem wszelkich zmian w jego właściwościach lub stanie. Ale to oczywiście nie jest rozsądne.

W miarę rozwoju aplikacji próba ponownego renderowania i porównywania całego wirtualnego DOM przy każdym działaniu może w końcu spowolnić.

React zapewnia programiście prosty sposób na wskazanie, czy komponent wymaga ponownego renderowania. W tym miejscu w grę wchodzi metoda shouldComponentUpdate .

 function shouldComponentUpdate(nextProps, nextState) { return true; }

Gdy ta funkcja zwraca wartość true dla dowolnego komponentu, umożliwia wyzwolenie procesu render-diff.

Daje to prosty sposób kontrolowania procesu render-różnic. Za każdym razem, gdy chcesz zapobiec ponownemu renderowaniu komponentu, po prostu zwróć wartość false z funkcji. Wewnątrz funkcji możesz porównać bieżący i następny zestaw właściwości oraz stan, aby określić, czy konieczne jest ponowne renderowanie:

 function shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id; }

Korzystanie z React.PureComponent

Aby nieco uprościć i zautomatyzować tę technikę optymalizacji, React udostępnia tak zwany „czysty” komponent. React.PureComponent jest dokładnie taki jak React.Component , który implementuje funkcję shouldComponentUpdate() z płytkim porównaniem właściwości i stanów.

React.PureComponent jest mniej więcej odpowiednikiem tego:

 class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this.props, nextProps) && shallowCompare(this.state, nextState); } … }

Ponieważ wykonuje tylko płytkie porównanie, może okazać się przydatne tylko wtedy, gdy:

  • Twoje rekwizyty lub stany zawierają prymitywne dane.
  • Twoje właściwości i stany zawierają złożone dane, ale wiesz, kiedy wywołać forceUpdate() , aby zaktualizować swój komponent.

Niezmienność danych

Co by było, gdybyś mógł użyć React.PureComponent , ale nadal mieć skuteczny sposób na automatyczne rozpoznanie, kiedy jakiekolwiek złożone właściwości lub stany zmieniły się automatycznie? W tym miejscu niezmienne struktury danych ułatwiają życie.

Idea używania niezmiennych struktur danych jest prosta. Za każdym razem, gdy obiekt zawierający złożone dane ulegnie zmianie, zamiast wprowadzać zmiany w tym obiekcie, utwórz kopię tego obiektu ze zmianami. Dzięki temu wykrywanie zmian w danych jest tak proste, jak porównanie odniesień do dwóch obiektów.

Możesz użyć Object.assign lub _.extend (z Underscore.js lub Lodash):

 const newValue2 = Object.assign({}, oldValue); const newValue2 = _.extend({}, oldValue);

Co więcej, możesz użyć biblioteki, która zapewnia niezmienne struktury danych:

 var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 2); assert(map1.equals(map2) === true); var map3 = map1.set('b', 50); assert(map1.equals(map3) === false);

Tutaj Immutable.Map jest dostarczany przez bibliotekę Immutable.js.

Za każdym razem, gdy mapa jest aktualizowana za pomocą metody set , nowa mapa jest zwracana tylko wtedy, gdy operacja set zmieniła wartość bazową. W przeciwnym razie zwracana jest ta sama mapa.

Możesz dowiedzieć się więcej o używaniu niezmiennych struktur danych tutaj.

Więcej technik optymalizacji aplikacji React

Korzystanie z wersji produkcyjnej

Podczas tworzenia aplikacji React otrzymujesz naprawdę przydatne ostrzeżenia i komunikaty o błędach. Dzięki temu identyfikacja błędów i problemów podczas opracowywania jest przyjemnością. Ale przychodzą one kosztem wydajności.

Jeśli zajrzysz do kodu źródłowego Reacta, zobaczysz wiele sprawdzeń if (process.env.NODE_ENV != 'production') . Te fragmenty kodu, które React uruchamia w twoim środowisku programistycznym, nie są potrzebne użytkownikowi końcowemu. W środowiskach produkcyjnych cały ten niepotrzebny kod można odrzucić.

Jeśli zainicjowałeś swój projekt za pomocą create-react-app , możesz po prostu uruchomić npm run build , aby wyprodukować wersję produkcyjną bez tego dodatkowego kodu. Jeśli używasz pakietu Webpack bezpośrednio, możesz uruchomić pakiet webpack webpack -p (który jest odpowiednikiem webpack --optimize-minimize --define process.env.NODE_ENV="'production'" .

Funkcje wiązania wcześnie

Bardzo często można zobaczyć funkcje powiązane z kontekstem komponentu wewnątrz funkcji render. Dzieje się tak często, gdy używamy tych funkcji do obsługi zdarzeń komponentów podrzędnych.

 // Creates a new `handleUpload` function during each render() <TopBar onUpload={this.handleUpload.bind(this)} /> // ...as do inlined arrow functions <TopBar onUpload={files => this.handleUpload(files)} />

Spowoduje to, że funkcja render() utworzy nową funkcję przy każdym renderowaniu. O wiele lepszym sposobem na zrobienie tego samego jest:

 class App extends React.Component { constructor(props) { super(props); this.handleUpload = this.handleUpload.bind(this); } render() { … <TopBar onUpload={this.handleUpload} /> … } }

Korzystanie z wielu plików porcji

W przypadku jednostronicowych aplikacji internetowych React często łączymy cały nasz front-endowy kod JavaScript w jednym zminifikowanym pliku. Działa to dobrze w przypadku małych i średnich aplikacji internetowych. Jednak wraz z rozwojem aplikacji dostarczanie tego dołączonego pliku JavaScript do przeglądarki może stać się procesem czasochłonnym.

Jeśli używasz Webpacka do tworzenia aplikacji React, możesz wykorzystać jego możliwości dzielenia kodu, aby podzielić kod zbudowanej aplikacji na wiele „fragmentów” i dostarczyć je do przeglądarki w razie potrzeby.

Istnieją dwa rodzaje dzielenia: dzielenie zasobów i dzielenie kodu na żądanie.

Dzielenie zasobów pozwala podzielić zawartość zasobów na wiele plików. Na przykład, używając CommonsChunkPlugin, możesz wyodrębnić wspólny kod (taki jak wszystkie zewnętrzne biblioteki) do własnego pliku „chunk”. Używając ExtractTextWebpackPlugin, możesz wyodrębnić cały kod CSS do osobnego pliku CSS.

Ten rodzaj podziału pomoże na dwa sposoby. Pomaga przeglądarce buforować te rzadziej zmieniające się zasoby. Pomoże to również przeglądarce wykorzystać równoległe pobieranie, aby potencjalnie skrócić czas ładowania.

Bardziej godną uwagi funkcją Webpack jest dzielenie kodu na żądanie. Możesz go użyć do podzielenia kodu na porcję, którą można załadować na żądanie. Dzięki temu początkowe pobieranie jest niewielkie, co skraca czas ładowania aplikacji. Przeglądarka może następnie pobierać inne fragmenty kodu na żądanie, gdy jest to wymagane przez aplikację.

Możesz dowiedzieć się więcej o dzieleniu kodu Webpack tutaj.

Włączanie Gzip na serwerze WWW

Pliki JS pakietu aplikacji React są zwykle bardzo duże, więc aby przyspieszyć ładowanie strony internetowej, możemy włączyć Gzip na serwerze WWW (Apache, Nginx itp.)

Wszystkie nowoczesne przeglądarki obsługują i automatycznie negocjują kompresję Gzip dla żądań HTTP. Włączenie kompresji Gzip może zmniejszyć rozmiar przesyłanej odpowiedzi nawet o 90%, co może znacznie skrócić czas pobierania zasobu, zmniejszyć zużycie danych przez klienta i skrócić czas pierwszego renderowania stron.

Sprawdź w dokumentacji serwera WWW, jak włączyć kompresję:

  • Apache: użyj mod_deflate
  • Nginx: użyj ngx_http_gzip_module

Korzystanie z wtyczki Eslint-react

Powinieneś używać ESLint w prawie każdym projekcie JavaScript. React nie jest inny.

Dzięki eslint-plugin-react , zmusisz się do dostosowania się do wielu reguł programowania w React, które mogą przynieść korzyści Twojemu kodowi na dłuższą metę i uniknąć wielu typowych problemów i problemów, które pojawiają się z powodu źle napisanego kodu.

Spraw, aby Twoje aplikacje internetowe React ponownie działały szybciej

Aby w pełni wykorzystać React, musisz wykorzystać jego narzędzia i techniki. Wydajność aplikacji internetowej React leży w prostocie jej komponentów. Przytłaczający algorytm różnicowania renderowania może spowodować, że Twoja aplikacja będzie działać słabo w frustrujący sposób.

Zanim będziesz mógł zoptymalizować swoją aplikację, musisz zrozumieć, jak działają komponenty React i jak są renderowane w przeglądarce. Metody cyklu życia Reacta umożliwiają zapobieganie niepotrzebnemu ponownemu renderowaniu komponentu. Wyeliminuj te wąskie gardła, a uzyskasz wydajność aplikacji, na którą zasługują Twoi użytkownicy.

Chociaż istnieje więcej sposobów optymalizacji aplikacji internetowej React, dostrajanie komponentów, aby aktualizowały się tylko wtedy, gdy jest to wymagane, zapewnia najlepszą poprawę wydajności.

Jak mierzyć i optymalizować wydajność aplikacji internetowej React? Udostępnij to w komentarzach poniżej.

Powiązane: Pobieranie danych nieaktualnych podczas ponownego sprawdzania poprawności za pomocą haków React: przewodnik