
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ę.

CSS Modules - kolejny sposób na style w React
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 wbudowana w css-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
):
.login {
background: #fff;
}
.input {
border: 1px solid #000;
}
.button {
color: #000;
background: #ff0000;
}
Domyślasz się już zapewne, że style te dotyczą komponentu formularza logowania. Spójrzmy więc teraz na ten komponent:
import React from 'react';
import styles from './LoginForm.css';
class LoginForm extends React.Component {
render() {
<form className={styles.login}>
<input className={styles.input} type="text" />
<input className={styles.input} type="password" />
<button className={styles.button} type="submit">Submit</button>
</form>
}
}
export default LoginForm;
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:
<form class="loginForm__login___1g3fw">
<input class="loginForm__input___w43a1" type="text" />
<input class="loginForm__input___w43a1" type="password" />
<button class="loginForm__button___2a1sh" type="submit">Submit</button>
</form>
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):
import React from 'react';
import styles from './LoginForm.css';
import CSSModules from 'react-css-modules';
class LoginForm extends React.Component {
render() {
<form styleName="login">
<input styleName="input" type="text" />
<input styleName="input" type="password" />
<button styleName="button" type="submit">Submit</button>
</form>
}
}
export default CSSModules(LoginForm, styles);
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:
<div className="global-style" styleName="module-style">...</div>
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-modules
. Wiąż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!