W ReactJS warto trzymać się pewnych sprawdzonych strategii. Na dobry początek wystarczy, że zapamiętasz kilka ważnych pojęć. Poznaj moją historię, jak na własnej skórze przekonałem się, że dziedziczenie się nie sprawdza.
Jakich wzorców i koncepcji używać w ReactJS?
Kiedyś często widziałem porównania Reakta do V w modelu MVC (model, view, controller). Posunąłbym się krok dalej i stwierdził, że cały front-end to wspomniany view 😜 Moja rada: nie próbuj odtwarzać wzorca MVC na front-endzie! Nie znasz tego wzorca? Nie szkodzi, to nie jest teraz Twój problem. Możesz teraz o nim w ogóle zapomnieć.
W zamian wolę stwierdzenie, że React to biblioteka do zarządzania warstwą UI (user interface). UI może też posiadać własną logikę i stan. Tak! Chyba mi nie powiesz, że to czy pup-up jest otwarty, będziesz przechowywać na serwerze w jakiejś bazie danych? Stara szkoła mówi, że w warstwie interfejsu nie powinna znajdować się logika biznesowa. Kiedy szkolę z front-endu osoby, które przychodzą z back-endu, zawsze spotykam ten niesmak:
JSX to kompletne pomieszanie logiki z widokiem!
Te obawy są słuszne, bowiem dobrze jest oddzielić logikę biznesową (JS) od implementacji widoków (HTML + CSS). Na front-endzie realizuje się to trzymając się zasad Progressive Enhancement.
Podział odpowiedzialności
Jeżeli tworzę pop-up, a w nim formularz, to gdzie zawrzeć obsługę stanu formularza? Czy w komponencie formularza, który znika wraz z pop-upem? Wraz z nim znika jego stan, który zakładam, że potrzebujesz? Nie? Ok, więc może w samym pop-upie? On też znika! Jeszcze jeden poziom wyżej? Ale jak mają wtedy przepływać dane, w sensie props?
To problemy, przed którym muszą stanąć adepci szkół programowania, bootcampów i innych kursów jedynie wprowadzających do Reakta. Z mojego doświadczenia wynika, że musiałem pracować ponad dwa lata codziennie zmagając się z tymi pytaniami, aby osiągnąć biegłość i intuicję przy dobieraniu właściwych wzorców.
Jakich wzorców i koncepcji używa się w Reakcie?
Jeśli przychodzisz spoza front-endu lub znasz już jakieś wzorce, to wiedz, że React też ma swoje własne wzorce. Pochodzą one w dużej mierze z programowania funkcyjnego. Nie musisz się przejmować, co programowanie funkcyjne znaczy. Zapoznaj z listą poniżej. To według mnie pojęcia, które powinien znać każdy architekt aplikacji ReactJS:
- hook
– render props, inversion of control (odwrócenie sterowania)
– higher order component (komponent wyższego rzędu)
– immutability (niemutowalność)
– one way data flow (jednokierunkowy przepływ danych)
– controlled and uncontrolled inputs (inputy kontrolowane i niekontrolowane)
– composition over inheritance (kompozycja zamiast dziedziczenia)
– pure function
– side effect (efekt uboczny)
– lazy loading, lazy evaluation
– provider, consumer
Przetłumaczyłem jedynie te, które warto wg mnie w ogóle tłumaczyć. W pozostałych raczej nie spotykam się z innymi nazwami niż angielskie. Osobiście nie widzę wielkiej potrzeby tłumaczenia tego na polski.
Z tej list najważniejszym pojęciem jest dla mnie „composition over inheritance” – „kompozycja zamiast dziedziczenia”. To filozofia, styl, bunt 😅
A teraz na poważnie…
Zamiast odnosić się do kompozycji, o której pewnie jeszcze nie raz napiszę, zapodam coś na temat temat dziedziczenia.
Jeden antywzorzec do zapamiętania
W Reakcie w ogóle nie sprawdza się dziedziczenie. Celowo to podkreślam, bo istnieje pokusa, aby spróbować je zastosować. Dotyczy to osób, które lubią programować obiektowo lub mają backendowy background.
W Reakcie można spotkać dzieczenie (inheritance) przy komponentach klasowych. Kiedy piszesz class Post extends React.Component
, to jest to właśnie dziedziczenie po klasie Component z Reakta. O komponentach klasowych możemy już powoli zapominać, ale to temat na osobną dyskusję. Jakkolwiek dziedziczenie jest wskazywane w dokumentacji Reakta jako antywzorzec. Oczywiście za wyjątkiem tego, które tu przytoczyłem, dziedziczenia po klasie Component!
At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies.
Jak dziedziczenie wypada w praktyce?
⚠️ To, co przeczytasz dalej to już historia. Mam nadzieję, że u Ciebie się nie powtórzy 😅
Z własnego doświadczenia powiem, że dziedziczenie wychodzi bardzo kiepsko. Dostałem kiedyś pod skrzydła projekt LEGACY 😱, gdzie dziedziczenie po własnych komponentach bazowych było praktyką. Pomimo tego, że jestem przyzwyczajony do takiej konstrukcji w innych językach i sam odczyt oraz zrozumienie kodu nie było dla mnie kłopotem, to zacząłem odczuwać pewne zakłopotanie.
Otóż nie zawsze byłem pewien, co dany komponent powinen robić. Jaka jest jego rola w projekcie? Kiedy rozszerzać klasę bazową a kiedy jej dziecko? Czasami nie wiedziałem skąd brały się metody, które otrzymywałem w this. Musiałem dużo skakać po plikach i szukać implementacji. Do tego dochodzą klasy abstrakcyjne i polimorfizm, które dodatkowo komplikowały sprawę. Czułem się jak obcym mieście bez telefonu.
W mojej opinii takie podejście było oznaką ignorancji dobrych praktyk. Jeżeli wydaje Ci się, że jakiś wzorzec jest dobry, bo znasz go z innych frameworków lub języków, to sprawdź czy sprawdzi się on również w ReactJS lub innej technologi, której właśnie używasz a jest dla Ciebie nowa.
A wystarczyło zajrzeć do dokumentacji, obejrzeć kilka tutoriali lub przeczytać artykuł dotyczący problemu, który chcemy rozwiązać. Wyszukiwarka internetowa przekieruje we właściwe miejsce po frazie „React inheritance”.
Dlaczego dziedziczenie nie działa w ReactJS?
Dzieje się tak ponieważ komponent ma ostatecznie zawsze to samo zadanie: zwrócić kod JSX. Zawsze musi być wywołana metoda render. Zakładając, że używam komponentów klasowych, mogę użyć też metod. Jednak w 95% przypadków można zastąpić metodę zwykłą funkcją lub innym komponentem. Czy to nie jest właśnie kompozycja? Ostatecznie celem utworzenia nowej metody jest sprawienie, aby kod był bardziej czytelny a metoda render nie spuchła do ekstremalnych rozmiarów.
W podejściu obiektowym każda klasa powinna realizować jakieś specjalne zadania. Wtedy też każdej metody klasy możemy użyć właściwie w dowolnym momencie. W ReactJS tak nie jest. Komponent wywołuje się jedynie w JSX. Jakoś tak naturalnie wychodzi, że wszystkie dodane przeze mnie metody są domyślnie prywatne. React już wie, co zrobić z komponentem w JSX. Wie, że będzie mu potrzebna tylko metoda render i Lifecycle Methods. Na koniec tylko wspomnę, że JSX odzwierciedla strukturę HTMLa. To też dobry przykład kompozycji.