Miniatura artykułu

Komponenty Serwerowe w React

13 minut

Skopiuj link

Data publikacji: 9/17/2024, 8:32:54

Ostatnia aktualizacja: 9/17/2024

Komponenty serwerowe Reacta (ang. React Server Components lub RSC) to relatywnie nowa technologia, która jednak już teraz, głównie za sprawą frameworka NextJS, odnotowuje znaczny wzrost popularności. Mimo że pierwsze wzmianki o niej pojawiły się już w 2020 roku, to w obecnej wersji Reacta (18.3.1 w momencie pisania artykułu) mamy nadal do czynienia z eksperymentalną wersją. 

Zanim jednak uznasz, że nie warto się tym jeszcze przejmować: zespół odpowiedzialny za rozwój Reacta zapowiada, że od wersji 19, która jest tuż za rogiem, komponenty serwerowe będą już w pełni stabilne. Jest to zatem idealny moment na zgłębienie tematu.

Czym są komponenty serwerowe?

Wielu programistów uważa, że komponenty serwerowe to nic innego jak SSR (Server Side Rendering), dlatego już na samym początku chcę zaznaczyć, że są to zupełnie różne, niezwiązane ze sobą techniki. Jakiś czas temu napisałem artykuł, w którym porównuję różne sposoby renderowania aplikacji napisanych w React, jednak wspominam w nim o RSC jedynie pobieżnie. W związku z tym, w dalszej części tego artykułu znajdziesz akapit poświęcony porównaniu tych dwóch podejść.

A teraz już do rzeczy. Jak sama nazwa wskazuje - komponent serwerowy istnieje jedynie po stronie serwera i tylko tam jego kod jest wykonywany. Do klienta (zazwyczaj jest to przeglądarka) trafia jedynie to, co dany komponent zwraca

Żeby w pełni zrozumieć poprzednie zdanie, musimy nieco zagłębić się w działanie Reacta. Zacznijmy od rzeczy kluczowej - kodu JSX, który znajduje się w komponentach. Nie jest on, ani HTML-em, ani JavaScriptem i musi zostać w jakiś sposób skompilowany zanim trafi do przeglądarki, żeby mógł być przez nią zrozumiany. 

Za ten krok odpowiedzialny jest Babel, jednak nic nie stoi na przeszkodzie, żeby zrezygnować z użycia JSX i ręcznie napisać kod, który w innym przypadku zostałby dla nas wygenerowany. Dzięki temu możemy zrezygnować z kompilowania naszego kodu.

Przykład powinien rozwiać wszelkie wątpliwości. Na początek stwórzmy prosty komponent z wykorzystaniem standardowej składni:

A teraz odtwórzmy ten sam komponent, wykorzystując jedynie zrozumiały dla przeglądarek JS:

Wykorzystując funkcję createElement, dostarczoną przez twórców Reacta, możemy “ręcznie”, bez wykorzystania JSX, stworzyć komponent, a jego działanie w niczym nie będzie się różnić od poprzedniej wersji. To, co powiem, jest znacznym uproszczeniem, ale właśnie to robi dla nas Babel - zamienia kod JSX na wywołania funkcji createElement i to właśnie ona jest wynikiem działania komponentu.

Jeżeli do tej pory zastanawiałeś się, jaki to ma związek z RSC, to teraz już wiesz - komponenty serwerowe zwracają do klienta jedynie “wykonany” kod, a więc efekt działania funkcji createElement, którym jest obiekt (na jego podstawie React wie, co powinien wyświetlić):

Podsumowując - RSC są wykonywane jedynie po stronie serwera, a ich kod nigdy nie trafia do przeglądarki. Zamiast tego odsyłany jest jedynie obiekt, będący efektem wywołania funkcji createElement. Na jego podstawie React w przeglądarce wie, co i gdzie powinien wyświetlić.

Zanim jednak przejdziemy dalej, muszę się przyznać do pewnego uproszczenia, które tu zastosowałem. Jeżeli zagłębimy się nieco bardziej w komponenty serwerowe, to okaże się, że React używa specjalnego formatu RSC payload, który tak naprawdę jest kodem binarnym i zawiera nieco więcej informacji niż te, które są zwracane z funkcji createElement. Są to jednak szczegóły implementacyjne, dlatego pozwoliłem sobie je pominąć, żeby ułatwić zrozumienie tematu.

Po co nam to?

Wiesz już, czym RSC jest, ale być może nadal zadajesz sobie pytanie: jakie korzyści niesie ze sobą ich użycie? Zanim odpowiem na to pytanie, poruszę jeszcze jedną kwestię, a konkretnie ich minusy.

W świecie programowania nie istnieją rozwiązania, które nie mają wad, a każda nowa technologia, język lub wzorzec mają zarówno plusy, jak i minusy. Również i w tym przypadku jest ich kilka:

  • Interaktywność: komponenty serwerowe nie mogą korzystać z API dostępnego tylko po stronie klienta, np. useState, useEffect, onClick. Nie są zatem zawsze odpowiednim wyborem. 

  • Propsy: wszystkie propsy przekazywane do komponentu serwerowego muszą być możliwe do serializowania. Jest to wewnętrzny proces Reacta, który (nieco upraszczając) zamienia komponenty w JSON. Problem polega na tym, że niektóre dane nie mogą zostać przesłane w tym formacie. Przykładem może być funkcja lub Symbol.

  • Integracja: komponenty klienta nie mogą importować komponentów serwerowych. Jest to ograniczenie wynikające z natury ich działania - kod ten wykonywany jest w przeglądarce, a jak wspomniałem wcześniej, komponenty serwerowe nigdy do przeglądarki nie trafiają. Wysłany zostaje jedynie efekt ich działania. Możemy jednak obejść to ograniczenie i umieścić RSC w komponentach klienta za pomocą kompozycji

  • Biblioteki: nie wszystkie biblioteki są przystosowane do używania z RSC. Przykładem może być niezwykle popularna paczka Styled Components. Aktualnie opiera się ona na contextcie, który nie jest dostępny poza przeglądarką (więcej na ten temat znajdziesz w tym wątku). Problemy z kompatybilnością bibliotek to temat na zupełnie osobny artykuł, jednak warto mieć to na uwadze podczas ich doboru. 

Jak widzisz, lista minusów jest długa, a na dodatek są one bardzo poważne. Na pocieszenie dodam, że lista zalet jest równie imponująca. Co w takim razie zyskujemy? 

  • Asynchroniczność: komponenty serwerowe mogą być asynchroniczne, a wszelkie zapytania do API mogą być wykonywane bezpośrednio w komponencie, bez użycia hooków.

  • Separacja: wspomniałem już, że RSC jest wykonywany jedynie na serwerze, a jego kod nigdy nie trafia do przeglądarki. Co za tym idzie, możemy bezpiecznie wykonywać zapytania do bazy danych bezpośrednio z komponentu. 

  • Bezpieczeństwo: ten punkt idzie w parze z poprzednim. Ponieważ kod komponentu nigdy nie zostaje wysłany do przeglądarki (trafi tam tylko wynik jego działania), możemy umieszczać w nim dane, które nie powinny być dostępne dla klienta.

  • Szybkość: wszelkie dynamiczne dane (np. pobierane z bazy danych) są dostępne zanim kod trafi do klienta. Dzięki temu nie musimy czekać na wczytanie kodu klienta, wykonanie zapytania i odpowiedź od serwera (jak to ma miejsce w przypadku komponentów klienta).

A zatem, komponenty serwerowe mają na celu przyspieszenie stron i aplikacji internetowych, przez przeniesienie kodu na serwer, co w przypadku wielu użytkowników, posiadających słabszy komputer lub wolniejsze łącze, może okazać się bardzo przydatne. Jeśli dodatkowo połączymy je z akcjami serwerowymi (ang. server actions), czyli kolejną nowością w React, to znacznie poprawiają także DX (ang. developer experience) i ułatwiają pobieranie wszelkiego rodzaju danych.

RSC, RSS i CSR

Dość powszechnym przekonaniem jest, że RSC ma zastąpić SSR. W praktyce jednak są to dwie zupełnie różne techniki, które w żadnym stopniu się nie wykluczają, a wręcz przeciwnie. Komponenty serwerowe doskonale współpracują z renderowaniem po stronie serwera. Zanim jednak przejdziemy do szczegółów, zacznijmy od ogółu. Czym właściwie różni się SSR od RSC?

Najłatwiej będzie to wytłumaczyć z pomocą prostego diagramu:

RSC odpowiada za wykonanie kodu komponentu po stronie serwera. Nie generuje on jednak kodu HTML, a wspomniany wcześniej RSC payload. W tym momencie możemy wykorzystać SSR do zamiany drzewa komponentów klienta (tak, komponenty klienta również mogą korzystać z SSR) oraz drzewa komponentów serwerowych w HTML, a następnie wysłać go do klienta (dla uproszczenia pomijam tu proces serializacji, stosowany przez Reacta. Jest to szczegół implementacyjny, który nie jest niezbędny do zrozumienia komponentów serwerowych).

Wiemy, że RSC i SSR się nie wykluczają, a nawet mogą się łączyć. Gdzie w tym wszystkim jest miejsce dla komponentów klienta?

Nie wszystko można osiągnąć przy użyciu komponentów serwerowych. Mówię tu przede wszystkim o interaktywności i stanie wewnątrz komponentu (useState, useEffect, useReducer, onClick, itd.), ale również o API dostępnych tylko w przeglądarce. RSC nie ma na celu zastąpić komponentów klienta, choć wielu programistów błędnie zakłada, że tak właśnie jest.

Ponownie, przedstawienie tego w sposób graficzny powinno pomóc:

Wykorzystaj moc nowych technologii tam, gdzie to możliwe, ale nie wahaj się stosować znanych Ci do tej pory rozwiązań i interaktywności tam, gdzie to konieczne. SSR, CSR, SSG i RSC mogą się ze sobą łączyć i nie wykluczają się wzajemnie (NextJS stosuje wszystkie). 

NextJS i RSC

Własnoręczna implementacja RSC w projekcie stanowi spore wyzwanie i wymaga od programistów znacznego nakładu pracy - trzeba być gotowym na skomplikowaną konfigurację bundlera (np. webpack), który będzie w stanie wygenerować dwa osobne grafy zależności (dla komponentów klienta i serwerowych), stworzyć logikę po stronie serwera odpowiedzialną za renderowanie, wykonywanie kodu komponentów, cache’owanie, przesyłanie lub streamowanie, itd.

Znacznie łatwiej jest skorzystać z frameworka. Od momentu nawiązania współpracy pomiędzy NextJS a React, ten pierwszy stał się branżowym standardem i obecnie posiada niemal monopol i jest domyślnym wyborem dla wielu firm i programistów. Zastosujemy go również w tym artykule.

Napiszmy pierwszy komponent serwerowy. Pamiętaj, że jego kod zostanie wykonany tylko po stronie serwera, a więc do dyspozycji mamy wszystkie funkcjonalności dostępne w NodeJS. Możemy również wykonywać bezpośrednie zapytania do bazy danych (w końcu jesteśmy na serwerze).

Zauważ, że w żaden sposób nie oznaczyłem tego komponentu jako “serwerowy”. Skąd w takim razie NextJS wie, że powinien go wykonać jedynie po stronie serwera?

Odpowiedź na to pytanie jest dość zaskakująca: NextJS stosuje podejście “server first”. Oznacza to, że wszystkie komponenty domyślnie są komponentami serwerowymi. Co prawda za tą ideą kryje się znacznie więcej, na przykład SSR, SSG, czy chociażby streaming, ale opisanie wszystkich tych koncepcji znacznie wykracza poza zakres tego artykułu. 

Jak w takim razie stworzyć komponent, który będzie wykonywany po stronie klienta? W tym celu React wymaga od nas zastosowania wymyślonej przez siebie konwencji. Na samej górze pliku, w którym znajduje się komponent, należy umieścić string “use client”. Gdybym zatem chciał przepisać utworzony wcześniej komponent serwerowy, na komponent klienta, to wyglądałoby to tak:

Podsumowując: wszystkie komponenty są domyślnie uznawane za serwerowe, chyba, że wyraźnie wskażemy inaczej. Jeżeli jednak chcemy zostać po stronie serwera, nie musi dodawać żadnego dodatkowego kodu. Nieco zamieszania w tym temacie wprowadziły akcje serwerowe, które wymagają zastosowania konwencji “use server”, jednak nie dotyczy to komponentów, a same akcje zasługują na osobny artykuł.

Podsumowanie

Komponenty serwerowe to prawdziwa rewolucja w świecie Reacta. Pozostaje pytanie, czy jesteśmy na nią gotowi?

Samodzielna ich implementacja jest wyjątkowo trudnym wyzwaniem, a jeśli połączymy ją ze statycznym generowaniem stron oraz SSR, to może się okazać, że zamiast tworzyć aplikacje internetowe, tworzymy nowy framework. Pozostaje nam zatem skorzystać z dostępnych na rynku rozwiązań, a może raczej rozwiązania, ponieważ NextJS jest obecnie jedynym frameworkiem wspierającym wszystkie trzy technologie, a jego główny konkurent - Remix, łączy się z biblioteką React Router, co przez wiele osób odbierane jest jako wycofanie się z rynku.

Wszystko więc wskazuje na to, że React staje się biblioteką, stanowiącą bazę dla twórców frameworków, które będą się na nim opierać i nawet oficjalna dokumentacja zaleca ich stosowanie w przypadku nowych projektów.

Problem polega na tym, że zmiany z wersji na wersję są naprawdę drastyczne, a twórcy bibliotek i oprogramowania open source po prostu za nimi nie nadążają, za co nie można ich winić - w końcu robią to za darmo w swoim wolnym czasie. Prawdopodobnie przyjdzie nam jeszcze trochę poczekać, zanim wszystkie, obecnie stosowane rozwiązania staną się kompatybilne z nowym podejściem.

Avatar: Wojciech Rygorowicz

Software Engineer / Fullstack developer

Wojciech Rygorowicz

wojciech.rygorowicz@gmail.com

Podziel się na

Dodaj komentarz

Komentarze (0)

Brak komentarzy