Nowości ze świata ReactJS. O trybie współbieżności w teorii i w praktyce
Tryb Współbieżności (Concurrent Mode) to nowość w świecie ReactJS i w momencie powstawania tego tekstu wciąż pozostaje w fazie eksperymentalnej, uważam jednak, że już teraz warto się nim zainteresować ze względu na to, jak duży może okazać się jego wpływ na przyszłość projektowania stron internetowych.
Spis treści
Czym jest cały ten Tryb Współbieżności?
W praktyce jest on zestawem nowych funkcjonalności, które mają pomóc aplikacji pozostać responsywną bez względu na moc urządzenia czy szybkość sieci, z której korzysta dany użytkownik. Tryb współbieżności przede wszystkim pozwala nam na:
- asynchroniczne renderowanie, dzięki któremu zmiany o większym priorytecie nie zostaną zablokowane przez te, które są mniej istotne (tzw. Time Slicing),
- zawieszenie renderowania nowego widoku na czas, ładowania danych, które są mu do tego potrzebne.
Renderowanie blokowane a przerywane
Posłużę się tutaj systemem kontroli wersji jako wyjątkowo trafną metaforą, która pomaga zrozumieć, jak działa Tryb Współbieżności. Dan Abramov użył jej podczas swojego wystąpienia na JSConf Iceland, gdzie świat po raz pierwszy usłyszał o nadchodzących zmianach.
Wracając do tematu: jeśli pracujesz w zespole, wysoko prawdopodobnym jest, że korzystacie z pomocy systemu kontroli wersji (przykładowo GIT-a), na którym pracujecie na gałęziach. Przygotowujecie zmianę i kiedy ta jest już gotowa, scalacie gałąź, na której pracowaliście z gałęzią główną (master), tak żeby inni mogli je później u siebie zaciągnąć. W ten sposób możecie równolegle pracować nad kilkoma zmianami, nie przeszkadzając sobie nawzajem.
Co by było, gdybyście nie korzystali z systemu kontroli wersji? W sytuacji, w której ktoś zacząłby wprowadzać zmiany w danym pliku, nikt inny nie mógłby nic w nim zmieniać, żeby nie doprowadzić do konfliktu – byłby dosłownie zablokowany.
Aktualnie, większość bibliotek do tworzenia UI, w tym także React, tak właśnie działa. Kiedy już obraz zacznie się renderować, proces ten nie może zostać przerwany – biblioteka jest w tym momencie dosłownie zablokowana. Dostrzegasz podobieństwo?
Inaczej sytuacja wygląda w Trybie Współbieżności, w którym proces renderowania jest przerywany, a my mamy większą kontrolę nad tym, w jaki sposób proces ten przebiega. Podobnie jak to wygląda w pracy na gałęziach, pomiędzy którymi można się przełączać, żeby jednocześnie wprowadzać więcej niż jedną zmianę, React może przerwać renderowanie niskopriorytetowego elementu, aby zrobić coś ważniejszego, a następnie, kiedy skończy, wrócić do rozpoczętej wcześniej pracy.
Tryb Współbieżności pozwala ograniczyć potrzebę użycia technik takich jak „debouncing” czy „throttling” – ponieważ proces renderowania w każdej chwili może zostać przerwany, React nie musi sztucznie opóźniać swojej pracy, a jedynie przerwać ją w momencie, w którym okaże się to niezbędne, aby aplikacja pozostała responsywna.
TL;DR
W skrócie: w Trybie Współbieżności React może pracować nad kilkoma zmianami stanu aplikacji jednocześnie – tak samo jak członkowie zespołu korzystający z systemu kontroli wersji mogą jednocześnie pracować nad nowymi funkcjonalnościami projektu na różnych gałęziach. W przypadku, gdy użytkownik posiada szybkie urządzenie i łącze, zmiany wydają się zachodzić w jednej chwili, natomiast jeśli użytkownik korzysta z urządzenia o małej mocy i wolnym łączu, aplikacja wciąż pozostaje responsywna. Koniec ze „zwieszaniem się”.
Suspense
React 16.6 przyniósł ze sobą komponent <Suspense>, który pozwala na wyświetlenie zdefiniowanego przez programistę kodu (docelowo wskaźnika ładowania) w trakcie czekania na dane lub kod.
Jak to wygląda w praktyce? Na potrzeby tego tekstu przygotowałem bardzo prostą aplikację, która pozwala na dynamiczne załadowanie komponentu w odpowiedzi na kliknięcie przycisku. Cała logika aplikacji zamyka się w kilku linijkach:
Kliknięcie przycisku wywołuje funkcję anonimową, w której – korzystając z hooka useState (podsyłam link do dokumentacji, na wypadek, jeśli czyta ten tekst ktoś niezaznajomiony z hookami) – zmieniamy wartość showImage na true. W tym momencie:
- React.lazy sięga po komponent <MysteriousImage />,
- <Suspense> wyświetla kod, zdefiniowany we własności fallback,
- <MysteriousImage /> dociera na miejsce,
- kod przypisany do własności fallback zostaje zastąpiony tym zaciągniętym przez React.lazy.
Zachęcam do wypróbowania aplikacji na CodeSandbox.io. Żeby łatwiej zaobserwować wspomniany efekt polecam też otworzyć konsolę w przeglądarce, a następnie wejść w zakładkę Network, rozwinąć listę Online i wybrać na niej Slow 3G. W Chrome’ie powinno to wyglądać mniej więcej tak:
Zazwyczaj kiedy zmienia się stan aplikacji oczekujemy, że zmiany będą od razu widoczne na ekranie. Istnieją jednak wyjątki od tej reguły, kiedy może zależeć nam na celowym opóźnieniu wyświetlenia zmian.
Przykładowo, kiedy przechodzimy do nowej strony, ale ani kod, ani dane potrzebne do jej wyświetlenia jeszcze się nie załadowały, może irytować nas widok pustej strony lub samego wskaźnika ładowania. Nie lepiej byłoby chwilę dłużej pozostać na wcześniejszej stronie?
Sytuacji, w których takie rozwiązanie mogłoby okazać się przydatne jest całe mnóstwo, a żeby nie posługiwać się ogólnikami, przytoczę tutaj konkretny przykład. W Ericsson napisaliśmy aplikację internetową, której celem jest pomoc w zarządzaniu zespołami. Na podstawie wizualizacji, które tworzy, można określić w jakich technologiach specjalizuje się dany zespół, czy też oddział firmy. W przypadku, kiedy korzysta z tego narzędzia Scrum Master zainteresowany jedynie swoim zespołem, danych, które mają zostać zwizualizowane, będzie stosunkowo niewiele, a aplikacja będzie działała płynnie.
W przypadku kiedy ktoś chciałby spojrzeć na sprawę w sposób bardziej globalny i porównać, w jakich technologiach specjalizują się pracownicy oddziału w Krakowie, z tymi z Linköping w Szwecji, danych do wyświetlenia będzie znacznie więcej. W tym przypadku takie celowe opóźnienie wyświetlania nowego widoku mogłoby znacznie poprawić doświadczenia płynące z korzystania z aplikacji. Nic nie można poradzić na ilość danych, które muszą zostać przetworzone, możemy jednak na moment zatrzymać użytkownika przy starym widoku (który cały czas pozostaje responsywny), a w międzyczasie, w pamięci pracować już nad nowym widokiem. Dzięki temu czas pomiędzy przejściem z jednego stanu aplikacji do drugiego (z perspektywy użytkownika) stanie się znacznie krótszy, a korzystanie z niej przyjemniejsze.
We wcześniejszych wersjach Reacta byłoby to trudne do osiągnięcia, lecz w Trybie Współbieżności mamy dostęp do narzędzi, które znacznie ułatwiają podobne zadania. W celu ich przetestowania napisałem prostą aplikację, która ma za zadanie wyświetlić podane w formularzu słowo w formie zapisanej dużymi literami. Jest dostępna na CodeSandbox.io – zapraszam do testowania.
W momencie kliknięcia przycisku „TO THE UPPERCASE!” wywołana zostanie funkcja, która symuluje wysłanie zapytania na serwer, gdzie podane przez nas słowo zostaje przerobione na formę zapisaną dużymi literami, a następnie zwrócone. Pytanie brzmi: co zrobić z czasem, w którym użytkownik czeka na odpowiedź z serwera? Najlepiej byłoby przyspieszyć ten proces, lecz co nam pozostaje, jeśli jest to niemożliwe? Możemy sprawić, że czas oczekiwania wyda się użytkownikowi krótszy, dzięki zastosowaniu pewnego tricku wizualnego. W tym celu React oferuje nam nowy wbudowany hook – useTransition().
Żeby go użyć musimy wykonać trzy kroki:
1. Upewnić się, że używamy Trybu Współbieżności – w skrócie, w pliku index.js powinniśmy użyć funkcji ReactDOM.createRoot() zamiast klasycznego ReactDOM.render():
2. Zaimportować useTransition hook:
3. Użyć go:
useTransition() zwraca dwie wartości:
- startTransition – funkcję, której używa się do określenia, jaką zmianę stanu chcemy opóźnić,
- isPending – wartość logiczną prawda/fałsz, która określa, czy aplikacja jest w trakcie przejścia pomiędzy dwoma stanami.
Cały kod znajduje się na CodeSandbox.io – zwróć uwagę, w jaki sposób wykorzystane zostały wartości zwrócone przez useTransition() i koniecznie przetestuj, w jaki sposób zmiana wartości TRANSITION_CONFIG.timeoutMs oraz opóźnienia określonego wewnątrz funkcji fetchUppercaseText wpłynie na działanie aplikacji.
Podsumowanie
Pamiętajmy, że Tryb Współbieżności to wciąż bardzo świeży temat wewnątrz społeczności ReactJS, a zagłębiając się w nim natrafimy na wiele niewiadomych. Co więcej, ze względu na to, że wciąż znajduje się w fazie eksperymentalnej, wiele może się zmienić w sposobie, w jaki będziemy z niego korzystać w ostatecznej formie. Jeżeli więc jesteś osobą, która ceni sobie stabilność, może lepiej będzie, jeśli na jakiś czas zapomnisz o Trybie Współbieżności. W Ericsson trzymamy rękę na pulsie i już planujemy, w jaki sposób będziemy mogli wykorzystać wspomniane funkcjonalności, jednak z wprowadzaniem planów w życie czekamy na ich stabilną wersję.
Pomimo tych drobnych ostrzeżeń jest to bardzo ekscytujący czas dla wielu programistów i jeśli chcesz być dobrze przygotowanym na nadchodzące zmiany, to dobrym pierwszym krokiem będzie stosowanie trybu ścisłego (strict mode) – aplikacje, które w nim nie działają, mogą mieć problem z Trybem Współbieżności.
Osobiście nie mogę doczekać się momentu, w którym pojawi się stabilna wersja omówionych nowości. Myślę, że ich wpływ na jakość doświadczeń czerpanych przez użytkownika, szczególnie na urządzeniach mobilnych i w przypadku słabego zasięgu, może okazać się ogromny. Jestem bardzo ciekawy, w jaki sposób rozwinie się ten temat, i mam nadzieję, że skoro dotarłeś aż tutaj, to i Ciebie udało mi się nim zainteresować.
Pomyślnego kodowania!
Zdjęcie główne artykułu pochodzi z unsplash.com.