Кратко
СкопированоCSS-in-JS — это подход, при котором стили пишутся непосредственно в JavaScript-коде компонентов, а не в отдельных CSS-файлах. Это позволяет использовать возможности JavaScript для динамического управления стилями и обеспечивает изоляцию стилей на уровне компонентов, предотвращая конфликты и облегчая поддержку кода.
Существуют разные решения CSS-in-JS, но их объединяет общий принцип: стили описываются непосредственно в JavaScript-коде. Это может делаться с помощью объектов, шаблонных строк или через вызов специальных функций, которые обрабатывают определения стилей. Независимо от конкретного синтаксиса, эти JavaScript-описания затем преобразуются таким образом, что стили применяются к компонентам через уникальные идентификаторы. Чаще всего это автоматически сгенерированные CSS-классы — например, вместо общего класса .button
может быть создан уникальный класс вроде .acz123
или .
. Такой подход обеспечивает изоляцию стилей на уровне компонентов, предотвращая конфликты, и упрощает управление ими.
Вот несколько популярных CSS-in-JS библиотек: Styled Components, Emotion, Styled JSX и vanilla-extract.
Почему CSS-in-JS?
СкопированоГлавное преимущество использования подхода CSS-in-JS — возможность использовать конструкции JavaScript при описании стилей компонента. В зависимости от проекта, это может помочь описывать динамические стили, которые зависят от условий, а также упростить работу с темизацией.
Краткая история
СкопированоБегло рассмотрим историю появления CSS-in-JS решений.
Становление CSS
СкопированоCSS (Каскадные таблицы стилей) начал развитие в 90-е годы, был отмечен консорциумом W3C и начал стремительно развиваться. CSS стал методом определения стиля веб-документа. Он был мощным, чтобы удовлетворить потребности проектов тех лет, и был относительно прост, чтобы все желающие могли приступить к работе в короткий срок.
Появление реактивных фреймворков и SPA
СкопированоДо 2010-х годов, в основном создавались многостраничные приложения с четким разделением ответственности:
- HTML — определял разметку страницы,
- CSS — описывал, как они будут выглядеть,
- JavaScript — отвечал за интерактивность.
С течением времени развивался не только CSS, но и JavaScript — это приводит к появлению новой экосистемы разработки и современных реактивных фреймворков: Angular (2010), React (2013), Vue.js (2014). Разработка приложений начинает смещаться от многостраничных приложений на разработку одностраничных приложений (SPA), а пользователи стали ожидать от них более динамичного и сложного поведения.
Кристофер Шедо, его доклад о проблемах CSS и разработка первых решений CSS-in-JS
СкопированоВ 2014 году Кристофер Шедо (инженер известной социальной сети) выступает с докладом о 7 проблемах CSS, которые нужно решить для беспроблемной разработки больших динамичных приложений:
-
Глобальное пространство имён
Все CSS-селекторы живут в одной глобальной области, из-за чего стили разных компонентов могут конфликтовать или перезаписывать друг друга. -
Зависимости
При большом числе стилей сложно понять, в каком порядке подключать файлы и какие компоненты от чего зависят, что может привести к нежелательным коллизиям. -
Удаление неиспользуемого кода
В больших проектах часто копится CSS, который больше нигде не применяется, и его непросто безопасно убрать, не зная точного использования. -
Минификация
При сжатии стилей важно сохранять их читабельность для отладки, но и уменьшать размер, чтобы повысить производительность. -
Общий доступ к константам
Трудно централизованно хранить и переиспользовать значения (например, цвета, размеры), чтобы все команды работали с едиными стилевыми параметрами. -
Проблемы каскада
Когда несколько правил стилизуют одни и те же элементы, порядок подключения CSS может приводить к непредсказуемым результатам и конфликтам. -
Изоляция
Нужен способ гарантировать, что стили каждого компонента не затронут стили других, что особенно актуально в больших проектах.
В этом же году начинается работа над CSS-in-JS. Первыми экспериментальными подходами можно считать решения, появившиеся в экосистеме React. Одна из самых ранних и широко известных библиотек — JSS. Чуть позже начали появляться и другие проекты, но именно JSS часто упоминают как одну из первых полноценных реализаций «CSS в JavaScript».
Runtime и Zero-runtime
СкопированоЗа время развития CSS-in-JS появилось множество вариантов реализации CSS-in-JS. От тех, что предполагают полную обработку и генерацию стилей непосредственно в браузере пользователя («Runtime»), до решений, которые практически всю эту работу переносят на этап сборки проекта («Zero-runtime»). Ниже мы рассмотрим ключевые особенности этих двух основных направлений и их специфику.
Runtime подход
СкопированоСуть подхода
СкопированоСтили формируются и добавляются в DOM во время исполнения (рантайма) приложения. В процессе рендера или при изменении состояний создаются (или обновляются) соответствующие <style>
или классы в браузере.
Плюсы
Скопировано- Легко менять стили на лету в зависимости от состояния, темизации и так далее.
- Удобно изолировать логику стилизации в самих компонентах, сохраняя стиль и логику рядом.
Минусы
Скопировано- Дополнительная нагрузка в рантайме: при каждом рендере может происходить генерация и монтирование новых стилей, что влияет на производительность.
- Возможны сложности и повышенные требования к настройке серверного рендеринга (SSR) и кеширования стилей.
Библиотеки, реализующие подход
Скопировано- Styled Components
Использует шаблонные литералы для описания стилей внутри JavaScript/TypeScript. Генерирует уникальные классы в рантайме и монтирует их в DOM. - Emotion
Также генерирует стили во время исполнения. Позволяет писать стили через шаблонные литералы (styled
) или объектный синтаксис (css
). - JSS
Основан на использовании JavaScript-объектов, которые конвертируются в CSS в рантайме.
Zero-runtime подход
СкопированоСуть подхода
СкопированоСтили вычисляются и извлекаются на этапе сборки. В результате в браузер попадает статический (или почти статический) CSS, а логика генерации стилей не исполняется или почти не исполняется.
Плюсы
Скопировано- Нет накладных расходов в браузере (минимальный дополнительный JS-код).
- Благодаря пункту выше обычно получаются улучшенные показатели производительности и метрик загрузки.
- Проще реализовать серверный рендеринг, так как стили уже готовы к моменту, когда HTML отдается клиенту.
Минусы
Скопировано- Ограничения в динамических стилях: большая часть логики должна быть статически рассчитана во время сборки, поэтому зависимые от состояний стили, могут быть менее гибкими.
- Сложность настройки сборки: обычно требует дополнительной настройки сборщиков (Webpack, Vite и другие).
Библиотеки, реализующие подход
Скопировано- Linaria
Анализирует CSS, написанный в виде шаблонных литералов, на этапе сборки и извлекает его в отдельные файлы. В рантайме остаются только классы. - Vanilla Extract
Пишется на TypeScript/JS, но генерирует классические CSS-файлы во время сборки, практически без JS для стилизации на клиенте.
Основы синтаксиса
СкопированоСинтаксис у большинства решений очень похож. Рассмотрим основы синтаксиса на псевдокоде. За подробностями лучше заглянуть в документацию к конкретной библиотеке.
Простейший компонент
СкопированоНиже показан пример объявления компонента-кнопки, стили для которого описаны прямо внутри JavaScript-кода. Благодаря функции styled
создаётся новый компонент, наследующий все свойства обычной кнопки, но обладающий заданными стилями.
import {styled} from "css-in-js" // псевдобиблиотека для примераconst Button = styled('button')` color: white; background-color: black; border: none;`export default Button;
import {styled} from "css-in-js" // псевдобиблиотека для примера const Button = styled('button')` color: white; background-color: black; border: none; ` export default Button;
Динамические стили
СкопированоНиже показана функция, которая генерирует динамические стили для фона зависимости от флага is
. При true
функция вернёт синий цвет фона, при false
— серый.
import {css} from "css-in-js" // псевдобиблиотека для примераfunction getBackgroundColor(isPrimary) { return css` background-color: ${isPrimary ? 'blue' : 'gray'}; &:hover { background-color: ${isPrimary ? 'darkblue' : 'darkgray'}; } `;}export {getBackgroundColor};
import {css} from "css-in-js" // псевдобиблиотека для примера function getBackgroundColor(isPrimary) { return css` background-color: ${isPrimary ? 'blue' : 'gray'}; &:hover { background-color: ${isPrimary ? 'darkblue' : 'darkgray'}; } `; } export {getBackgroundColor};
В первом фрагменте создаётся функция get
, которая возвращает блок CSS с разными цветами фона в покое и при наведении курсора в зависимости от аргумента is
.
Во втором примере показано, как этот фрагмент может переиспользоваться: в новом компоненте Button
вызывается get
и результат вставляется в шаблон стилей, «включая» логику динамической стилизации, уже описанной в функции. Это удобно, если нужно единообразно управлять цветами фона в разных компонентах, сохранив их стили в одном месте.
import {styled} from "css-in-js" // псевдобиблиотека для примераimport {getBackgroundColor} from "./utils.js"const Button = styled('button')` color: white; border: none; ${getBackgroundColor(true)};`export default Button;
import {styled} from "css-in-js" // псевдобиблиотека для примера import {getBackgroundColor} from "./utils.js" const Button = styled('button')` color: white; border: none; ${getBackgroundColor(true)}; ` export default Button;
На практике
Скопированосоветует Скопировано
🛠️ Какую библиотеку выбрать? Чаще всего выбор подхода к CSS-in-JS определяется требованиями к производительности, объёмом динамики в интерфейсе и готовностью настраивать сборку. При этом часто всё сводится к двум простым сценариям:
- Если нужна «минимальная боль» при использовании динамических стилей и важна поддержка большого сообщества — часто берут Emotion или Styled Components.
- Если же приоритет — производительность и нет потребности в постоянном рендере уникальных стилей, можно обратить внимание на Linaria или Vanilla Extract.