Styled components - zaawansowane zagadnienia

W poprzednim artykule zapoznałem Was ze styled-components. Dzisiaj zanurkujemy głębiej w ten temat. Pokażę Wam co jeszcze można osiągnąć przy ich użyciu.

W artykule poruszę bardziej zaawansowane zagadnienia związane z CSSem, nie będę ich opisywał zakładając, że masz już o nich pojęcie. Pokażę za to, jak wykorzystać je w styled-components w połączeniu z Reactem. Jeżeli zagadnienia z artykułu nie będą Ci znane to zerknij do sekcji linków na końcu - na pewno znajdziesz tam kilka przydatnych linków. Nie przedłużając, ruszajmy!

Przekazywanie propsów

Biblioteka styled-components pozwala przekazywać dane z komponentów Reactowych bezpośrednio do definicji stylu. Dzięki temu w stylach posiadamy dostęp do dynamicznych wartości pochodzących np. ze stanu komponentu, którego dotyczą. Przykładowymi sposobami wykorzystania tej techniki mogą być:

  • sterowanie widocznością fragmentu;
  • reprezentowanie aktywności komponentu na przykład: aktywny obiekt zaznaczony zielonym kolorem, a nieaktywny - czerwonym;

Poniżej dwa przykłady przekazywania propsów i ich wykorzystania:

// Definicja komponentów
<Styled.SquareButton size={100} onClick={onSquareButtonPressed}>
 Wciśnij mnie!
</Styled.SquareButton>
<Styled.ToggledButton isVisible={isButtonVisible}>
 A ja się schowam
</Styled.ToggledButton>
// Style
export const SquareButton = styled.button`
 width: ${(props) => props.size}px;
 height: ${(props) => props.size}px;
`;

export const ToggledButton = styled.button`
 display: ${(props) => (props.isVisible ? "block" : "none")};
`;

Zwróć uwagę, że w przypadku SquareButton bezpośrednio wykorzystujemy przekazaną wartość “size”, żeby ustawić wysokość i szerokość kwadratu. W definicji stylu ToggledButton natomiast sprawdzamy zmienną i uzależniamy styl od jej wartości. Jeżeli jest true - pokazujemy button, jeżeli jest false - chowamy go.

Pseudoselektory, pseudoelementy i zagnieżdżanie

Styled-components pozwala na korzystanie z pseudoselektorów i pseudoelementów wewnątrz definicji stylu komponentu. Biblioteka wspiera składnię SCSS’ową, co oznacza, że można zagnieżdżać definicje stylów w sposób hierarchiczny. Zagnieżdżenia polecam stosować tylko w dwóch przypadkach:

  • gdy nie jesteś w stanie inaczej wpłynąć na styl dostarczany przez bibliotekę UI z której korzystasz,
  • gdy tworzysz złożone style, które trudno zdefiniować bez utworzenia hierarchii (np. hover na rodzicu zmieniający styl elementu potomnego).

Poniżej znajdziesz dwa przykłady obrazujące wykorzystanie pseudo- i zagnieżdżania:

export const Paragraphs = styled.div`
 display: flex;
 flex-direction: column;
 justify-content: center;
 align-items: center;
 text-align: center;

 div {
   background: antiquewhite;

   :first-child {
     border: 1px solid red;
   }

   p {
     color: navy;

     &.classy-paragraph {
       color: brown;
     }

     &#unique-paragraph {
       background: aliceblue;
     }
   }
 }
`;
export const HoverableBox = styled(Box)`
 transition: 300ms all;
 background: aqua;

 :hover {
   background: aquamarine;
 }
`;

Dodatkowe atrybuty przekazywane do DOM

Styled-components ma swój własny sposób przekazywania atrybutów do elementów DOM. W HTMLu po prostu zdefiniowalibyśmy dany atrybut i nadali mu wartość. Tutaj definiujemy je przy pomocy funkcji “attrs”. Można to traktować jako zaletę lub wadę, dla mnie zdecydowanie jest zaletą. Dlaczego wada? Bo niestety musimy pamiętać o użyciu funkcji. A dlaczego zaleta? Największymi zaletami jakie widzę jest to, że nie "zaśmiecamy" rendera atrybutami oraz to, że będą one przekazane do każdego elementu opisanego danym stylem. Żeby to nieco zobrazować - jak stworzę PasswordInput, który będzie miał nadany atrybut “type” równy “password” to wszystkie PasswordInputy będą go miały - nie muszę się powtarzać.

Warto pamiętać, że funkcja attrs w parametrze przyjmuje propsy przekazane z komponentu. Oznacza to, że możemy stworzyć input i przy pomocy propsów wysterować jego typ (podobnie jak to robiliśmy w HTML).

Są dwa sposoby określania atrybutów styled-componentu:

Pierwszy z nich obejmuje zdefiniowanie stałego obiektu opisującego atrybuty przypisane do elementu DOM.

export const PasswordInput = styled.input.attrs({
 type: "password",
})``;

Drugi sposób wykorzystuje wcześniej wspomnianą funkcję.

<Styled.TestableBox testID={"test-box"}>
export const TestableBox = styled(Box).attrs((props) => ({
 "data-testid": props.testID,
}))`
 background: blanchedalmond;
`;

A tutaj screen pokazujący jaki jest wynik wygenerowany w DOM: styled attributes

Style globalne

Kolejną techniką, która może się okazać przydatna przy tworzeniu aplikacji jest definiowanie stylów globalnych. Korzystając z klasycznego CSS było to dziecinnie proste - definiując styl domyślnie miał on zakres globalny. W przypadku styled-components style domyślnie są enkapsulowane, tak by były widoczne tylko w obrębie danego komponentu.

Aby osiągnąć efekt globalnych stylów w musimy zdefiniować styl przy użyciu funkcji createGlobalStyle. Kolejnym krokiem jest “wyrenderowanie” ich w dowolnym miejscu aplikacji - zazwyczaj najlepszą lokalizacją jest komponent wejściowy aplikacji.

W przykładzie poniżej znajdziecie przekazany props “theme” - przejdę do niego w kolejnym akapicie, w którym omówię tworzenie motywów kolorystycznych.

import { createGlobalStyle } from "styled-components";

export default createGlobalStyle`
 body {
   background: ${(props) => props.theme.background || "#fff"};
   color: ${(props) => props.theme.color || "#333"};
 }

 h3 {
   margin: 1rem;
 }
`;

Wewnątrz dowolnego komponentu:
import GlobalStyle from "./Globals/Globals";

<GlobalStyle />

Tworzenie motywów kolorystycznych

Powszechnym przypadkiem użycia kaskadowych arkuszy stylów jest tworzenie motywów pozwalających na szybkie przełączanie się między wersjami kolorystycznymi. Przy użyciu CSS można to było osiągnąć poprzez zdefiniowaniu klasy dla każdego wariantu kolorystycznego i sterowanie nimi przy pomocy JSa.

Styled-components pozwala na osiągnięcie podobnego efektu poprzez udostępnienie ThemeProvidera. Jest to komponent dostarczający motyw do wszystkich komponentów potomnych przy pomocy Reactowego Context API.

Biblioteka zapewnia kilka sposobów na tworzenie motywów. Ja jednak skupię się na - według mnie - najprostszym z nich. Jeżeli chcecie poczytać nieco więcej o pozostałych, na końcu artykułu umieszczę link do źródła.

Przechodząc do implementacji - dobrze jest najpierw zdefiniować kilka motywów, a następnie podać wybrany z nich do ThemeProvidera jako domyślny. Następnie w komponencie zawierającym ThemeProvider możemy utworzyć stan, który będzie zmieniał wybrany motyw. Zmiana motywu zostanie rozpropagowana do komponentów potomnych ThemeProvidera.

Poniżej znajdziecie przykładowe użycie, w którym zmieniam tło i czcionkę zdefiniowane w globalnym stylu (takie combo, a co!).

Definicje motywów:

export const LIGHT_THEME = {
 background: "#fff",
 color: "#333",
};

export const DARK_THEME = {
 background: "#333",
 color: "#fff",
};

Komponent zawierający ThemeProvider:

const [theme, setTheme] = useState(LIGHT_THEME);

...

<ThemeProvider theme={theme}>
 <GlobalStyle />
</ThemeProvider>

Komponent potomny, który w propsach otrzymał setTheme, służący do zmiany motywu:

<Styled.DarkThemeButton onClick={() => setTheme(DARK_THEME)}>
 Ciemny motyw
</Styled.DarkThemeButton>
<Styled.LightThemeButton onClick={() => setTheme(LIGHT_THEME)}>
 Jasny motyw
</Styled.LightThemeButton>

Mamy to!

To by było na tyle z naszej krótkiej przygody ze styled-componentsami. Poruszyłem najczęściej wykorzystywane zagadnienia w pracy z biblioteką. Jeżeli jesteście dalej głodni wiedzy to znajdziecie więcej zagadnień w oficjalnej dokumentacji, do której link znajdziecie poniżej.

Rozszerzyłem przykład z poprzedniego artykułu o dzisiejsze zagadnienia. Jeżeli chcecie sami pomajsterkować w jego kodzie źródłowym to znajdziecie go tutaj.

Przydatne linki:

Do góry