Polub bloga na fejsie!

CSS Modules – kolejny sposób na style w React

6

Sposobów i podejść do nadawania stylów komponentom React jest całe mnóstwo. Sam jakiś czas temu pisałem o jednym z nich – styled components. Dziś przedstawię kolejny z nich – CSS Modules – z którym zetknąłem się, poniekąd z przymusu, podczas pracy nad moim projektem Polski Frontend. Myślę, że wspominałem już, że do budowy front-endu do tego projektu wykorzystałem starter react-starter-kit – otóż miał on już wszystko skonfigurowane pod CSS Modules, postanowiłem więc dać temu szansę.

No, a dziś przyszedł czas na przedstawienie z czym to się je… Zapraszam do lektury!

CSS Modules – o co chodzi?

Myślę, że nie będę się tutaj wdawał w szczegóły konfiguracyjne bo w przypadku Reacta nie ma wielkiej filozofii. Każdy, jeśli tylko będzie chciał, znajdzie wszystko co trzeba w dokumentacji projektu.  Teraz wystarczy jeśli napiszę, że opcja modułów CSS jest wbudowanacss-loader dla webpacka (wystarczy włączyć flagę modules przy konfiguracji loadera). W końcu piszę to w kontekście Reacta, a w aplikacjach tworzonych przy użyciu tej biblioteki webpack jest na porządku dziennym. Nie znaczy to oczywiście, że tylko z Reactem można go używać – i o tym właśnie więcej we wspomnianej dokumentacji.

Ok, skoro kwestie instalacyjno-konfiguracyjne mamy omówione, przejdźmy do ogólnej idei CSS Modules. Jak zawsze, najlepiej będzie zacząć od przykładu! Najpierw spójrz na plik CSS (LoginForm.css):

Domyślasz się już zapewne, że style te dotyczą komponentu formularza logowania. Spójrzmy więc teraz na ten komponent:

Pierwsze, na co chciałbym, abyś zwrócił uwagę jest import z pliku LoginForm.css. Plik ten jest tutaj traktowany jako pełnoprawny moduł JavaScript, który przypisujemy do zmiennej styles.

Kolejna interesująca rzecz dzieje się w metodzie render komponentu LoginForm. Zwróć przede wszystkim uwagę na atrybuty className poszczególnych elementów JSX oraz sposób w jaki wykorzystano tam zmienną styles!

To właśnie jest „magia” modułów CSS – dzięki ich zastosowaniu, mamy możliwość traktować pliki CSS jak moduły JavaScript, a poszczególne klasy zdefiniowane w tym pliku jak właściwości obiektu.

Wyrenderowany HTML

Warto jeszcze spojrzeć na kod HTML będący wynikiem renderowania powyższego komponentu:

Hmm… W sumie nawet trochę to przypomina BEM, prawda? Ok, to „rozkmińmy” co oznaczając poszczególne człony powyższych, wygenerowanych klas CSS…

Najpierw mamy loginForm co odpowiada nazwie całego komponentu. Czyli w sumie blok z BEM, prawda? Następnie mamy nazwę klasy z pliku CSS czyli element. Na końcu natomiast widać jakiś losowy ciąg znaków…

Te znaki na końcu mają zapobiegać ewentualnym konfliktom nazw. Chodzi o to, abyśmy w wielu różnych komponentach mogli zdefiniować taką samą nazwę klasy, powiedzmy „item”. W każdym z tych komponentów może ona przecież znaczyć coś zupełnie innego i mieć zupełnie inne style.

Zady i walety (według mnie)

Na początek kilka negatywnych odczuć jaki mi się nasunęły podczas pracy z modułami CSS:

  • po pierwsze, według mnie, odwoływanie się do każdego ze stylów poprzez obiekt jest mniej wygodne niż zwykłe podawanie nazw klas jako ciąg znaków
  • poza tym, jesteśmy przez powyższe zmuszeni do używania notacji „camelCase” dla nazw klas CSS, co trochę kłóci się z ogólnie przyjętymi obecnie praktykami (ewentualnie można robić tak: styles['class-name'] ale to trochę karkołomne)
  • do tego dochodzą jeszcze problemy z łączeniem modułów CSS ze stylami globalnymi, które nie są modułami czy też ze stylami z zewnętrznych bibliotek (np. Bootstrap czy FontAwesome) – teoretycznie zakłada się, że przy CSS Modules w ogólnie nie powinno być stylów globalnych ale jak to w życiu, to nie jest to aż takie proste do osiągnięcia…

Z drugiej strony podejście to ma też pewne zalety:

  • dzięki modułom CSS da radę (przynajmniej w WebStorm) korzystać z podpowiedzi dostępnych w danym module klas
  • na pewno też podejście to pomaga to w enkapsulacji stylów w komponencie – dany styl musi znajdować się w zaimportowanym pliku CSS aby można go było użyć

Ostatnie co napisałem (o enkapsulacji), to zdaje się, główna motywacja do stworzenia tego podejścia. Faktycznie nawet się to sprawdza – pamiętam, że kiedy zaczynałem z Reactem to często zdarzało mi się stworzyć komponent wraz ze stylami do niego. Później, w miarę jak komponent rósł, wydzielałem z niego mniejsze komponenty. Niby wszystko fajnie tyle, że zdarzało się, czy to z lenistwa, czy z braku czasu, że wszystkie style zostawały w komponencie „bazowym”…

Jeśli używasz CSS Modules to jesteś przed takim czymś zabezpieczony – jeśli przeniesiesz tylko kod, a style zostaną w starym komponencie to po prostu przestaną one działać. Trzeba więc przenosić wszystko razem, dzięki czemu trochę trudniej o bałagan.

React CSS Modules

Powyżej wymieniłem kilka negatywnych odczuć jakie mi się nasunęły podczas korzystania z CSS Modules w React. Okazuje się, że można im wszystkim łatwo zaradzić dzięki bibliotece react-css-modules!

W myśl zasady, że 16 linii kodu jest więcej warte niż 1024 słowa (no dobra, suchar – przepraszam), spójrzmy na lekko przerobiony komponent LoginForm z poprzedniego przykładu (bibliotekę react-css-modules możesz w sposób standardowy zainstalować z npm):

No dobra to teraz spójrzmy co się zmieniło. Pierwsza rzecz to doszedł import z pakietu react-css-modules (użyjemy go na końcu).

To co bardziej rzuca się w oczy to to, że tym razem do klas zdefiniowanych w pliku CSS nie odnosimy się poprzez obiekt styles! Zamiast tego zwyczajnie używamy ich nazw jako ciągów znaków (czyli nie musimy mieć nazw w notacji „camelCase”).

Jeżeli jesteś spostrzegawczy to zapewne rzuciła Ci się w oczy jeszcze jedna istotna zmiana – otóż zamiast standardowego atrybutu className do ustawiania klas użyto atrybutu styleName. Dzięki zastosowaniu odrębnego, dedykowanego atrybutu uzyskano dodatkowo fajny efekt uboczny:

W ten sposób łatwo odróżnić, które style są globalne, a które należą do danego komponentu. Poza tym nie trzeba kombinować w przypadku kiedy chcemy użyć jednocześnie stylów globalnych i tych modułowych (bez tej biblioteki trzeba korzystać z wstrzykiwania parametrów do ciągów znaków: className={global-style ${style.someClass}}).

Na koniec zwróć jeszcze uwagę na eksport klasy komponentu. Jak widzisz użyto tam metody CSSModule zaimportowanej wcześniej z modułu react-css-modulesWiąże ona komponent z modułem stylów i zwraca odpowiednio przetworzony komponent wynikowy.

Podsumowanie

Po tym jak chwilę popracowałem z CSS Modules w projekcie React, mam teraz dość mieszane uczucia. Z jednej strony, wspomniana enkapsulacja oraz pomaganie w jej pilnowaniu jest na pewno czymś przydatnym.

Z drugiej strony były też elementy, które mnie denerwowały. Wydaje się, że biblioteka taka jak react-css-modules może po części temu zaradzić. Mówiąc jednak szczerze, nie pracowałem z nią jeszcze „produkcyjnie” więc na ten moment są to tylko domysły poparte „researchem”, którego efekt mogłeś przeczytać powyżej. Poza tym jest to jednak swego rodzaju „hack” i na przykład „wrapowanie” każdego jednego komponentu może być upierdliwe szczególnie jak do tego dorzucimy jeszcze inne „wrappery” (choćby connect z Reduxa).

P.S. Jak wspomniałem, sposobów na style w React jest sporo i chyba czas by się przyjrzeć również pozostałym – myślę więc, że w najbliższym czasie będzie więcej wpisów w tym temacie!

REACT, REDUX, REACT-ROUTER - KURSY ON-LINE

Chcesz od podstaw poznać tajniki React, Redux oraz react-router? Zapraszam do moich szkoleń on-line:

Przejdź do szkoleń

Uwaga! Obecnie trwa przedsprzedaż kursów - premiera 1 sierpnia 2017!

  • Ale wiesz, że Shadow DOM daje Ci _prawdziwą_ enkapsulację, a nie taką oszukiwaną jak w CSS Modules? 😉

    CSS Modules wprowadziło koncept lokalnych stylów, niemniej IMO nie jest to najlepsza z możliwych implementacji.

    • No tak, ale w wpisie opisuję enkapsulację na poziomie komponentów React… Żeby móc skorzystać z zalet ShadowDOM w React trzeba się posiłkować rozwiązaniami typu https://github.com/Wildhoney/ReactShadow – na pierwszy rzut oka mnie to odrzuca 😉

      Poza tym zgadzam się – tak jak napisałem, nie uważam aby ta implementacja była jakaś super, a wpis nie ma na celu wychwalania tego rozwiązania 😉

      • W sumie deklaratywny Shadow DOM ma swój urok (bo w sumie pozwala wypluć sensowny kod z serwera).

        Zastanawia mnie, czy da się pomieszać WC z Reactem na tej zasadzie, że React będzie wyłącznie rendererem, a całość będzie się odbywać wewnątrz Custom Elements.

        • Strzelam, że dałoby radę – nie robiłem akurat tego ale widziałem wykorzystanie Reacta właśnie tylko do renderowania

          • Tylko że tu się pojawia problem… Bo VDOM Reacta raczej nie lata po Shadow DOM. Więc diffowanie musiałoby zachodzić dla każdego komponentu oddzielnie, hmm.

            Trudna sprawa 😀

  • Łukasz Pniewski

    Z reactem miałem do tej pory niezbyt wiele do czynienia, ale wydaje mi się, że są lepsze rozwiązania, które są bardzie „naturalne”. Mi osobiście najbardziej przypadł do gustu „styled componentes”. https://github.com/styled-components/styled-components Napisał to ten sam ziomek co css modules. Chętnie się dowiem, co myślicie na ten temat.

Google Analytics Alternative