Nie zapominaj o PureComponent w React
Tworzenie aplikacji w React to tworzenie komponentów i łączenie ich w jedną całość. Komponenty mogą mieć stan lub też nie. React API dostarcza nam kilka sposobów na tworzenie komponentów. Mamy “Functional Component”, “Class Component” i “Pure Component”. Dziś chciałbym podzielić się moim doświadczeniem i wiedzą z pracy z Pure Component, bo jest to część React’a, o której my – deweloperzy często zapominamy.
Jan Michalak. Lead Fronted Developer w Redvike. Poznawanie nowych technologii sprawia mi wielką frajdę, ale daje też lepszą perspektywę przy wyborze narzędzi do realizacji projektów. Tak samo w życiu prywatnym poznawanie nowych rzeczy, sprawia mi wielką radość. Lecz na długiej liście zainteresowań wyróżnia się piwowarstwo domowe, kibicowanie Realowi Madryt oraz oglądanie/uprawianie narciarstwa zjazdowego.
Podstawowym konceptem, na którym opiera się działanie React’a jest przebudowywanie (rerender) komponentów na skutek zmian lokalnego stanu komponentu, bądź wartości jakie ten komponent przyjmuje (propsów).
Gdy tworzymy aplikację, każdy render wykorzystuje część pamięci. To nie będzie problemem, gdy mamy prostą aplikację, na kilku komponentach, uruchomioną na dobrym komputerze. Natomiast gdy tych komponentów są setki, ma to wpływ na wydajność naszej aplikacji. Szczególnie, gdy dużo komponentów naraz i często musi przeładować się na urządzeniu o ograniczonej ilości pamięci, wtedy szybkość działania aplikacji spada, możemy również zauważyć “przycinanie się” pewnych animacji
W takich sytuacjach przydaje się właśnie Pure Component, który daje nam możliwość optymalizacji liczby renderów naszego komponentu. W tym artykule pokażę Wam, jak działa to pod maską i jak mądrze korzystać z tego typu komponentu w React.
Spis treści
Różnica między Component, a Pure Component
Gdy tworzymy Pure Component, robimy to bardzo podobnie do tworzenia Class Component. Natomiast różnicą jest to, że zamiast dziedziczyć z klasy React.Component, dziedziczymy je z React.PureComponent.
import React from 'react' // Standard Class Component class MyComponent extends React.Component { render () { return ( <div>MyComponent</div> ) } } // PureComponent class MyPureComponent extends React.PureComponent { render () { return ( <div>MyPureComponent</div> ) } }
Pierwsze pytanie, jakie może teraz przyjść do głowy — to w jaki sposób Pure Component jest lepszy? Cała “magia” opiera się na tym, w jak różny sposób działa checkShouldComponentUpdate
w zależności od klasy komponentu. Kod tej funkcji znajdziecie tutaj.
Za każdym razem gdy Class Component dostaje nową wartość, stan lub kontekst jest on przeładowywany. Możesz wyobrazić to sobie jako Class Component, który w metodzie shouldComponentUpdate
zawsze zwraca true.
W przypadku PureComponent, React porównuje obecne parametry w komponencie do nowych i jeżeli wartości są takie same (nie zmieniły się), nie dochodzi do przeładowania. Natomiast jeżeli się różnią, komponent jest przeładowywany.
Jak działa Pure Component? Po części teoretycznej, czas na kod. W tym przykładzie występują dwie rakiety w metodzie ładowania komponentu RocketStation.
<Rocket altitude={this.state.altitude} /> <PureRocket altitude={this.state.altitude} />
Jedna jest przykładem Class Component, druga Pure Component. Mamy też dwa przyciski:
- Fly! – przy każdym kliknięciu zwiększa nasz stan wysokości głównego komponentu aż osiągnie on 100.
- Touch down – zeruje stan wysokości, sprowadza rakiety na ziemię.
Przetestujemy teraz naszą aplikację, by zobaczyć jak to działa. Jak widzisz w konsoli Class Component przeładowuje się przy każdym przesłaniu do niego parametrów, nieważne, czy są różne od obecnych, czy nie. Natomiast Pure Component gdy klikamy Touch Down kilka razy pod rząd nie zmienia koloru i nie wysyła do konsoli kolejnych powiadomień. To samo ma miejsce, gdy wysokość rakiet osiągnie 100 i klikamy cały czas “Fly!”.
Dzieje się tak właśnie dlatego, że React sprawdza dla Pure Component, czy przesyłane parametry są różne od ostatnich. Jeżeli nie, Pure Component nie jest przeładowywany. W tym prostym przykładzie, od razu widać, jak łatwo zminimalizowaliśmy liczbę przeładowań za pomocą Pure Component.
Oczywiście, zazwyczaj sytuacja nie będzie tak prosta i możesz chcieć przeładowywać komponent, tylko wtedy, gdy na przykład określona wartość zostanie zmieniona i nie zależy Ci na obserwowaniu zmian pozostałych parametrów. Wtedy przydaje się zrozumienie działania metody shouldComponentUpdate
i jej działania. Możesz ją zaimplementować w Class Component, by określić warunki jej działania, a tym samym – kiedy nasz komponent będzie przeładowywany, a kiedy nie.
shouldComponentUpdate (nextProps, nextState) { return this.state.altitude != nextState.altitude }
Praca z Pure Component
Pure Component to zdecydowanie prosty i przyjemny sposób to optymalizacji naszej aplikacji pisanej w React. Natomiast trzeba pamiętać o tym, jak to działa i czego unikać. Przede wszystkim czego unikać ze względu na powierzchowne porównywanie (shallow comparision) jakiego React w tym przypadku dokonuje i o którym wspominałem wyżej.
By zapobiec przyszłym problemom warto znać dwie zasady przydatne przy pracy z Pure Component.
Unikaj deklarowania funkcji w renderze, gdy używasz Pure Component
render() { return ( <PureRocket onStart={() => { console.log('rocket starts') }}/> ) }
Deklarowanie anonimowych funkcji z Pure Component w funkcji render sprawia, że każda aktualizacja komponentu – rodzica wywołuje przeładowanie tego komponentu typu Pure Component, co nie ma sensu, bo nie sprawdzamy, czy zaszła zmiana w parametrach. To dlatego, że przy każdym renderze funkcja deklarowana jest na nowo. Dlatego lepiej jest deklarować funkcję w Class Component i w ten sposób unikać takiej sytuacji.
onPureRocketStart = () => console.log('rocket starts') render() { return ( <PureRocker onStart={this.onPureRocketStart} /> ) }
Szczególnie zalecam, by korzystać z tej techniki nawet gdy nie korzystamy z Pure Component, bo w ten sposób i tak ograniczamy nadmierne użycie zasobów.
Nie deklaruj wartości w funkcji render.
render() { const fuel = 100 return <PureRocket rocketFuel={fuel} /> }
To podobna sytuacja do deklarowania funkcji w renderze. Ładowanie wartości w render() sprawia, że przeładowujemy nasz Pure Component nawet, gdy przekazywane parametry się nie zmieniają.
By tego uniknąć należy przenieść deklarowaną wartość poza funkcję render do stanu komponentu.
state = { fuel: 100 } render() { return <PureRocket rocketFuel={this.state.fuel} /> }
Gdy wartość jest stała i nie potrzebujesz jej zmieniać, możesz ją zadeklarować jako wartość klasy.
fuel = 100 render() { return <PureRocket rocketFuel={this.fuel} /> }
W ten sposób funkcja przeładowałaby się tylko wtedy, gdy zmieniłaby się wartość fuel.
React.memo
Jeśli chciałbyś uzyskać te samą możliwość szybkiego porównywania paramterów w przypadku komponentu funkcyjnego to możesz skorzystać z React.memo, o którym więcej napisałem tutaj.
Podsumowując
Mam nadzieję, że tym artykułem zachęciłem Cię do pisania lepiej zoptymalizowanego kodu, podczas tworzenia aplikacji w React. Warto pamiętać o tych możliwościach, jakie dostarczają twórcy Reacta, by nasz kod działał wydajniej. Jednak zawsze warto zastanowić się, które rozwiązanie w danej sytuacji będzie dla nas lepsze. Ponieważ w niektórych przypadkach idealnym rozwiązaniem będzie wykorzystanie PureComponent, natomiast w innych warto zainwestować trochę czasu w implementację shouldComponentUpdate
.
Artykuł został pierwotnie opublikowany na redvike.com. Zdjęcie główne artykułu pochodzi z unsplash.com.