Kurs JavaScript i React · Moduł 14: Zaawansowane stylowanie

CSS-in-JS - Zaawansowane Systemy Nawigacyjne

4 min czytania
W tej lekcji5

Styled-components to nie jedyna biblioteka CSS-in-JS. Istnieją inne, każda z unikalnymi zaletami. Poznajmy dwie najważniejsze: Emotion i vanilla-extract.

Emotion

Emotion to biblioteka CSS-in-JS, która oferuje dwa sposoby używania:

API obiektowe (@emotion/react)

1/** @jsxImportSource @emotion/react */
2import { css } from '@emotion/react';
3
4function SpacePanel({ isActive }) {
5  return (
6    <div css={css`
7      background: #0d1137;
8      border: 2px solid ${isActive ? '#00e676' : '#3949ab'};
9      border-radius: 12px;
10      padding: 20px;
11      transition: border-color 0.3s ease;
12    `}>
13      <h2>Navigation Panel</h2>
14    </div>
15  );
16}

Styled API (@emotion/styled)

1import styled from '@emotion/styled';
2
3const Card = styled.div`
4  background: #1a1a4e;
5  padding: 20px;
6  border-radius: 12px;
7`;
8
9// Lub obiektowa składnia:
10const CardObj = styled.div({
11  background: '#1a1a4e',
12  padding: 20,
13  borderRadius: 12,
14});

Zalety Emotion

  • Dwa API -- możesz używać template literals lub obiektów JS
  • Lekki -- mniejszy bundle niż styled-components
  • Wysoka wydajność -- zoptymalizowany runtime
  • Prop css -- możesz dodać style bezpośrednio przez prop css

Vanilla-extract -- Zero-runtime CSS-in-JS

Vanilla-extract to zupełnie inne podejście: style są generowane w czasie kompilacji, nie w runtime. Zero JavaScript w bundlu klienta!

1// styles.css.ts
2import { style, globalStyle } from '@vanilla-extract/css';
3
4export const panel = style({
5  background: '#0d1137',
6  border: '2px solid #3949ab',
7  borderRadius: 12,
8  padding: 20,
9  ':hover': {
10    borderColor: '#7c4dff',
11  },
12});
13
14export const title = style({
15  color: '#7c4dff',
16  fontSize: 24,
17});
1// Component.tsx
2import { panel, title } from './styles.css.ts';
3
4function SpacePanel() {
5  return (
6    <div className={panel}>
7      <h2 className={title}>Panel</h2>
8    </div>
9  );
10}

Zalety vanilla-extract

  • Zero runtime -- brak dodatkowego JS w przeglądarce
  • TypeScript-first -- pełne typowanie, autouzupełnianie
  • Bezpieczne typy -- błąd kompilacji gdy używasz nieistniejącego stylu
  • Theming -- wbudowany system tematów ze zmiennymi CSS

Porównanie CSS-in-JS

CechaStyled-componentsEmotionVanilla-extract
RuntimeTakTakNie (zero-runtime)
BundleWiększyŚredniMinimalny
TypeScriptDobre wsparcieDobre wsparcieNatywny TS
SSRWymaga konfiguracjiWymaga konfiguracjiBezproblemowy
DXBardzo dobryBardzo dobryWymaga buildu

Kiedy użyć której biblioteki?

Wybór biblioteki CSS-in-JS zależy od kilku czynników:

Wybierz styled-components gdy:

  • Twój zespół zna już tę bibliotekę -- jest najpopularniejsza i ma największą społeczność
  • Potrzebujesz bogatego ekosystemu pluginów i narzędzi
  • Zależy ci na czytelności kodu -- tagged template literals są intuicyjne dla programistów znających CSS

Wybierz Emotion gdy:

  • Chcesz elastyczności -- możesz używać zarówno template literals, jak i obiektów JS
  • Zależy ci na mniejszym rozmiarze bundla
  • Używasz Next.js lub innego frameworka, gdzie Emotion ma lepszą integrację
  • Potrzebujesz propa css do szybkiego dodawania stylów inline-like, ale z pełną mocą CSS

Wybierz vanilla-extract gdy:

  • Wydajność jest priorytetem numer jeden -- zero JS w bundlu klienta
  • Używasz TypeScript i chcesz pełne bezpieczeństwo typów
  • Budujesz dużą aplikację, gdzie rozmiar bundla ma znaczenie
  • Nie potrzebujesz dynamicznych stylów opartych na runtime props

Trend: zero-runtime CSS-in-JS

Coraz więcej projektów przechodzi na rozwiązania zero-runtime, takie jak vanilla-extract, Panda CSS czy Linaria. Powód? W dużych aplikacjach runtime CSS-in-JS (styled-components, Emotion) może spowalniać renderowanie, ponieważ generuje style w JavaScript podczas działania aplikacji.

Zero-runtime generuje klasy CSS w czasie kompilacji -- to jak przygotowanie mapy nawigacyjnej przed startem zamiast obliczania trasy w locie. Efekt: szybsze ładowanie strony i lepsze wyniki Core Web Vitals.

1// Panda CSS -- kolejna popularna zero-runtime alternatywa
2import { css } from '../styled-system/css';
3
4function Panel() {
5  return (
6    <div className={css({
7      bg: 'slate.900',
8      p: '4',
9      rounded: 'xl',
10      border: '2px solid',
11      borderColor: 'indigo.700'
12    })}>
13      Panel content
14    </div>
15  );
16}

Niezależnie od wyboru biblioteki, kluczowe jest trzymanie się jednego podejścia w ramach projektu i ustalenie konwencji z zespołem.

Kod do tej lekcji: App.jsx
1import React from 'react';
2import './styles.css';
3
4// Pokaz roznych CSS-in-JS: Emotion, vanilla-extract
5// (W Sandpack nie mozemy uzywac Emotion/vanilla-extract,
6//  wiec demonstrujemy skladnie w formie porownania)
7
8function CodeBlock({ title, code, language }) {
9  return (
10    <div className="code-block">
11      <div className="code-header">
12        <span className="code-lang">{language}</span>
13        <span className="code-title">{title}</span>
14      </div>
15      <pre className="code-content">{code}</pre>
16    </div>
17  );
18}
19
20function ComparisonSection({ title, children }) {
21  return (
22    <div className="section">
23      <h2 className="section-title">{title}</h2>
24      {children}
25    </div>
26  );
27}
28
29export default function App() {
30  return (
31    <div className="app">
32      <h1 className="heading">CSS-in-JS: Emotion vs Vanilla-extract</h1>
33      <p className="subtitle">Zaawansowane biblioteki stylowania</p>
34
35      <ComparisonSection title="Emotion -- css prop">
36        <CodeBlock
37          title="Inline styles z css prop"
38          language="JSX"
39          code={`/** @jsxImportSource @emotion/react */
40import { css } from '@emotion/react';
41
42function Panel({ isActive }) {
43  return (
44    <div css={css\`
45      background: #0d1137;
46      border: 2px solid \${isActive ? '#00e676' : '#3949ab'};
47      padding: 20px;
48    \`}>
49      Panel content
50    </div>
51  );
52}`}
53        />
54        <div className="feature-list">
55          <div className="feature">
56            <span className="feature-icon good">+</span>
57            <span>Prop css -- style bezposrednio na elemencie</span>
58          </div>
59          <div className="feature">
60            <span className="feature-icon good">+</span>
61            <span>Mniejszy bundle niz styled-components</span>
62          </div>
63          <div className="feature">
64            <span className="feature-icon good">+</span>
65            <span>Obsluguje skladnie obiektowa i template literals</span>
66          </div>
67        </div>
68      </ComparisonSection>
69
70      <ComparisonSection title="Vanilla-extract -- zero runtime">
71        <CodeBlock
72          title="Style generowane w czasie kompilacji"
73          language="TypeScript"
74          code={`// styles.css.ts
75import { style } from '@vanilla-extract/css';
76
77export const panel = style({
78  background: '#0d1137',
79  border: '2px solid #3949ab',
80  padding: 20,
81  ':hover': {
82    borderColor: '#7c4dff',
83  },
84});
85
86// Component.tsx
87import { panel } from './styles.css.ts';
88<div className={panel}>Panel</div>`}
89        />
90        <div className="feature-list">
91          <div className="feature">
92            <span className="feature-icon good">+</span>
93            <span>Zero runtime -- brak JS do stylow w bundlu</span>
94          </div>
95          <div className="feature">
96            <span className="feature-icon good">+</span>
97            <span>TypeScript-first -- pelne typowanie</span>
98          </div>
99          <div className="feature">
100            <span className="feature-icon warn">!</span>
101            <span>Wymaga konfiguracji buildowania</span>
102          </div>
103        </div>
104      </ComparisonSection>
105
106      <div className="comparison-table">
107        <h2 className="section-title">Porownanie</h2>
108        <table>
109          <thead>
110            <tr>
111              <th>Cecha</th>
112              <th>Styled-comp.</th>
113              <th>Emotion</th>
114              <th>Vanilla-extract</th>
115            </tr>
116          </thead>
117          <tbody>
118            <tr><td>Runtime JS</td><td>Tak</td><td>Tak</td><td className="highlight">Nie</td></tr>
119            <tr><td>Bundle size</td><td>Duzy</td><td>Sredni</td><td className="highlight">Minimalny</td></tr>
120            <tr><td>TypeScript</td><td>Dobre</td><td>Dobre</td><td className="highlight">Natywny</td></tr>
121            <tr><td>SSR</td><td>Konfiguracja</td><td>Konfiguracja</td><td className="highlight">Bezproblemowy</td></tr>
122            <tr><td>DX</td><td className="highlight">Swietny</td><td className="highlight">Swietny</td><td>Dobry</td></tr>
123          </tbody>
124        </table>
125      </div>
126    </div>
127  );
128}

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Która biblioteka CSS-in-JS generuje style w czasie kompilacji (zero runtime)?

  2. 2. Czym różni się Emotion z css prop od styled-components?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Układanie w pionie

    Ułóż etapy przetwarzania stylów w vanilla-extract od początku do końca:

  • Układanie w poziomie

    Ułóż elementy potrzebne do używania prop css w Emotion:

  • Edytor kodu

    Zaimplementuj rozwijany accordion z warunkowym clsx

  • Klikanie w kolejności

    Ułóż składnię definicji stylu w vanilla-extract:

Przydatne artykuły