Jak pisać CSS w komponentach React
Kiedy w 2013 roku Pete Hunt przekonywał publiczność do nowej mało znanej jeszcze biblioteki do tworzenia widoków, niewielu podejrzewało w jak błyskawicznym tempie to narzędzie zdobędzie serca na całym świecie. Tak się właśnie stało i społeczność front-endu na dobre przyjęła koncepcję pisania stron i aplikacji za pomocą komponentów.
Sama biblioteka doskonale radziła sobie z renderowaniem, obsługiwaniem zdarzeń i modyfikowaniem markupu. Każdy element interfejsu mógł być zamknięty wewnątrz komponentu i zostać wygodnie reużyty w wielu miejscach aplikacji, łącząc się z innymi komponentami zupełnie jak klocki lego.
UWAGA! W niniejszym artykule zakładam, że czytelnik/czytelniczka znają podstawy Reacta i umieją zbudować prostą aplikację za pomocą webpacka.
Komponenty i globalne arkusze stylów
Klasycznie taki komponent możemy wzbogacić o CSS za pomocą klasy:
// DummyElement.js
import React from 'react';
class DummyElement extends React.Element {
render() {
return <div className="container">Hey!</div>
}
}
/* styles.css */
.container {
font-size: 18px;
line-height: 1.4;
border-bottom: 1px solid black;
}
Pojawia się jednak problem, ponieważ generowany na wyjściu rezultat to wciąż standardowy dokument HTML z dołączonymi stylami. W takiej formie style mają zasięg globalny, więc przeglądarka nie rozróżni, czy dany styl ma być ograniczony do komponentu. Przeglądarka nawet nie będzie miała pojęcia, że istnieją tam jakiekolwiek komponenty. Jej zadanie to wziąć selektor i dopasować zdefiniowane pod nim reguły do wszystkich pasujących elementów w dokumencie.
Przy większej skali moglibyśmy narobić sobie niezłego bałaganu, gdzie style z jednego komponentu mogłyby nam przez przypadek zmodyfikować wygląd innego. Takie konflikty to zmora, nad którą niezmiernie trudno zapanować. Tandem JS-HTML potrzebuje sposobu, aby wewnątrz komponentu móc enkapsulować także CSS.
Inline styles
Pierwsza myśl, która przychodzi do głowy to bezpośrednie wrzucanie stylów do elementu za pomocą atrybutu style
:
import React from 'react';
const containerStyle = {
fontSize: 18,
lineHeight: 1.4,
borderBottom: '1px solid black'
};
class DummyElement extends React.Element {
render() {
return <div style={containerStyle}>Hey!</div>
}
}
Mamy teraz element, w którym style nie wyciekają na zewnątrz, nie mamy konfliktu nazw klas (bo klas nie ma w ogóle) i do tego jest pełna enkapsulacja.
Niestety w ten sposób tracimy naturalną dla CSS kaskadowość oraz nie możemy nawet użyć media queries co praktycznie dyskwalifikuje nam tę metodę.
Poza tym ja bardzo lubię tradycyjną składnię z selektorami. Musi być lepszy sposób.
CSS modules
Pierwszym naprawdę przyjemnym i skutecznym rozwiązaniem, z którym spotkałem się w pracy to CSS modules. Jest to wtyczka do PostCSS, który z kolei jest procesorem CSS dla webpacka, znanym głównie z tego, że potrafi dodać automatycznie wszystkie potrzebne vendor-prefixy w naszych stylach.
CSS modules pozwala nam zaimportować plik CSS tak jak każdy inny plik JS, a w zaimportowanym obiekcie dostajemy listę klas zdefiniowanych w tymże pliku:
import React from 'react';
import styles from './styles.css';
// { container: '2k4nr-container' }
class DummyElement extends React.Element {
render() {
return <div className={styles.container}>Hey!</div>
}
}
Jak widzimy, powyższy przykład nie różni się znacznie od pierwszego, gdzie zatem tkwi to magiczne rozwiązanie?
Otóż CSS modules podczas importowania pliku CSS generuje nowe nazwy dla naszych klas, zwracając nam stare nazwy jako klucze w importowanym obiekcie. W ten sposób narzędzie dodaje do klasy losowy ciąg znaków, powodując, że klasa ma unikalną nazwę w ramach całej aplikacji a dwie takie same klasy w dwóch różnych plikach CSS w ogóle ze sobą nie kolidują.
To wspaniałe w swojej prostocie rozwiązanie umożliwia nam pisanie zwyczajnego (lub bardzo zbliżonego) CSSa, ograniczając jednocześnie zasięg jego działania tylko do komponentów które go importują.
Ciekawostka: CSS modules są dostępne do użycia w create-react-app
.
Po dokładną specyfikację i instrukcję integracji, odsyłam do dokumentacji PostCSS i CSS modules.
CSS-in-JS
W społeczności front-endowej pojawiła się również inna koncepcja, nieco bardziej agresywna wobec tradycyjnych arkuszów stylów. Skoro piszemy w JS i tworzymy komponenty w JS, to style powinniśmy również traktować jako integralną część komponentu. Nie powinniśmy zatem przeskakiwać między plikami JS i CSS, wszystko powinno być w jednym miejscu.
To konsekwencja zmiany myślenia o naszej aplikacji jako podzielonej między trzy technologie – na myślenie o niej jako zbiorze komponentów. To komponent jest niepodzielną jednostką w takiej perspektywie, a nie markup, style i JS.
Poznajmy więc kolejne popularne podejście do tworzenia stylów – CSS-in-JS. Jako przykład weźmy najpopularniejszą bibliotekę z tej rodziny – Styled components
import styled, { css } from 'styled-components'
const Container = styled.div`
font-size: 18px;
line-height: 1.4;
border-bottom: 1px solid black;
`;
class DummyElement extends React.Element {
render() {
return <Container>Hey!</Container>
}
}
Powyższy przykład dość obrazowo pokazuje nam, że biblioteka styled-components
dostarcza zbiór prymitywnych elementów (div
, p
, a
, span
itp.), które możemy rozszerzyć o style używając skróconej notacji wywoływania funkcji zwanej tagged template strings
. Taka funkcja zwraca nam komponent który możemy użyć dalej w kodzie. Zauważ, że stosujemy tutaj zwyczajną składnię CSS.
Ponadto, możemy wewnątrz stylów czytać props
, na przykład:
const Container = styled.div`
font-size: ${props => props.size}px
`;
Biblioteka automatycznie rozpozna funkcję i wstrzyknie props
jako argument. Pamiętajmy też, że używając template strings, możemy wrzucić do środka dowolną wartość widoczną w bieżącym zasięgu:
const size = 18;
const Container = styled.div`
font-size: ${size}px;
`;
Styled components pozwala też na rozszerzanie już istniejących komponentów o nowe style, podmianę typu na inny (np. z a
na button
), używanie media queries, używanie globalnego, dynamicznego motywu ze zdefiniowanymi wartościami i wiele innych możliwości, po które odsyłam do dokumentacji.
Mamy więc do czynienia z rozwiązaniem, w którym możemy jednocześnie pisać CSS w znanej, tradycyjnej składni (wzbogaconej o składnie LESS), a jednocześnie cieszyć się jednolitym kodem, gdzie style bezpośrednio integrujemy z naszymi komponentami przekazując im wartości poprzez zwyczajne zmienne. Dodatkowo raz ostylowane komponenty możemy eksportować na zewnątrz aby użyć ich w wielu miejscach.
Ciekawostka: pamiętaj, aby w swoim IDE zainstalować wsparcie dla podświetlania i podpowiadania składni CSS w Styled components, to z pewnością ułatwi Twoją pracę.
Zachęcam do obejrzenia nagrania z prezentacji wyjaśniającej koncepcję Styled components:
Podsumowanie
Tworzenie aplikacji w oparciu o komponenty wymogło na deweloperach i deweloperkach innego podejścia do pisania CSS i wyłoniły się nowe sposoby integracji stylów z markupem.
Takich i podobnych bibliotek jest mnóstwo i codziennie powstają nowe. Pamiętajmy jednak, że wszystkie te sposoby to jedynie opakowania używane dla naszej wygody, ot kolejne biblioteki. Nie musimy znać wszystkich, a każde nowe narzędzie przychodzi z odpowiednią dokumentacją. Pod spodem zawsze jest doskonale nam znany i ustandaryzowany CSS, bo tylko taki rozpozna przeglądarka.