Это пролог к статье о Styled Components. Если вам интересно, как мы дошли до жизни такой, рекомендую начать именно отсюда. Сразу задам тон повествования: я предполагаю, что с JavaScript и React вы уже знакомы. Вам сейчас придётся согласиться, что HTML в JS это нормально, а CSS в JS, как минимум, тоже.
Компонентный подход#
Вы все, наверное, слышали понятие «разделение ответственности» (separation of concerns). Для веб-разработки вообще и вёрстки в частности это означало (и до сих пор означает, в целом) следующее: отделение структуры документа от внешнего вида и поведения. HTML отдельно, CSS отдельно, JavaScript отдельно. Но только ли это?
Вот нужна вам кнопка. Что вы делаете? Правильно: определяете её в HTML, составляете стили по её классу в CSS и пишете скрипт:
<button type="button" id="myButton" class="my-button">Нажми меняbutton>
<style>
.my-button {
border: 1px solid cyan;
background: transparent;
color: cyan;
}
style>
<script>
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', () => {
alert('My click!');
});
script>
Прекрасно, кнопка есть, можно использовать где угодно. Но где угодно ли? Она вот, в нашем документе лежит. Для начала, её нужно, как минимум, скопировать, а как максимум – озаботиться уникальным идентификатором, набросать классов для различного оформления, дописать JavaScript обработчики на каждую задачу... а потом всё это ещё и поддерживать в разных местах и таскать из проекта в проект. Да что там, даже в рамках одного проекта занятие муторное.
Давайте немного подумаем. А что, если разделение ответственности – оно не про отделение представления и поведения от структуры, а про разбиение большой задачи на куски поменьше, на...
Компоненты#
Каждый компонент решает свою задачу и решает её хорошо: кнопка реагирует на нажатие, поле ввода – откликается на ошибки пользователя, а модальное окно – само определяет, на каком устройстве оно было открыто. Ответственность разделена? Вполне, просто немного иначе:

Обратили внимание на цветовую кодировку классического разделения? Она никуда не делась и в компонентном подходе, а буквально размылась в нём: компонент должен полностью отвечать за свою структуру, поведение и внешний вид. Это довольно простой концепт, но многим до сих пор тяжело его принять.
JSX#
React стал одной из первых библиотек (гусары, молчать!), принёсшей компоненты в народ. Концепция JSX - HTML код совмещённый с JavaScript - была не новой, но создателям React удалось донести её в понятной форме массе веб-разработчиков.
JSX по определению решает лишь задачу объединения структуры части документа с поведением этой самой части:
export function MyButton(props) {
return (
<button type="button" className="my-button" onClick={props.handleClick}>
Нажми меня
button>
);
}
Вопрос же стилей при этом всё ещё открыт и по-умолчанию классы наше всё. Впрочем, гибкость React и JSX позволяет решить вопрос разными способами.
Vue.js и CSS Modules#
Ребята из Vue.js подошли к работе со стилями серьёзней. Давайте посмотрим на простой файловый компонент (.vue
):
<template>
<p class="paragraph"> World!p>
template>
<script>
module.exports = {
data: function () {
return {
greeting: 'Hello',
};
},
};
script>
<style scoped>
.paragraph {
font-size: 2em;
text-align: center;
}
style>
Что бросается в глаза? Мне – style scoped
. В экосистеме Vue.js используются CSS-модули на основе реализации от PostCSS. Область видимости стилей – текущий компонент, и собирается примерно в такое:
<style>
.paragraph[data-v-f3f3eg9] {
color: red;
}
style>
<template>
<p class="paragraph" data-v-f3f3eg9>hip>
template>
Аналогичный результат можно получить в любом современном фреймворке. В принципе, CSS-модули в совокупности с пользовательскими свойствами – это прекрасная альтернатива CJS.
Погоди, а с CSS-то что не так?#
Ну вообще-то, много чего:
- Всё написанное вами попадает в глобальную область видимости
Впрочем, это решается CSS-модулями или строгими соглашениями об именовании (кто сказал БЭМ?) - Управление зависимостями напоминает ад
Может быть решено препроцессорами, а в совокупности с модулями npm - становится достаточно похоже на то, что имеем в JS - Отследить неиспользуемый код очень сложно
Существуют инструменты вроде PurgeCSS и DropCSS, их настройка может быть непростой, но часть головной боли снимут, рекомендую ознакомиться - Код может стать довольно объёмным
Минификация и удаление неиспользуемого кода решают лишь часть проблем, в идеале решение должно быть комплексным и закрывать вопросы с длиной HTML-классов - Ещё не так давно не было стандартного способа задавать константы
Препроцессоры и пользовательские свойства FTW - «Жадность» определений
Стоит быть очень аккуратным при составлении API ваших компонентов и идти от простого к сложному, заранее продумывая все возможные состояния и варианты использования. - Возможности управления состоянием стилей из скриптов весьма ограничены и сложны
С приходом пользовательских свойств всё стало гораздо лучше - Ваш прекрасный набор классов для оформления запросто ломает некто, вооружённый * и >
Опять же, возвращаемся к проблеме глобальной области видимости. Всё, что туда попало и заранее известно – может быть переопределено

В сухом остатке, никто не мешает так продумать устройство ваших компонентов, чтобы не столкнуться с вышеописанными проблемами, но если половина из них может быть решена на корню иными инструментами – почему бы не попробовать? Встречайте...
CSS в JS#
Do it with style#
Мы и раньше могли обращаться к стилям DOM-узлов через свойство style, считывая и устанавливая значение свойства style связанного с узлом элемента:
el.style.color = 'white';
el.style.backgroundColor = 'cyan';
В итоге мы получаем генерированный атрибут style, который ещё называют инлайновыми стилями:
<div style="color: white; background-color: cyan">Hello Styles!div>
Минусы? Никаких вам псевдоклассов, никаких вам псевдоэлементов, никакой развитой анимации. Об отсутствии централизованного управления внешним видом множества элементов единовременно и объединения стилей (кажется, это худшая в мире ода CSS-классам) можно забыть. И об автоматических браузерных префиксах тоже. И о кешировании, естественно. И производительность, естественно, минимальная. Плюсы? Это бронебойно и точно работает (пока кто-нибудь !important не наставил).
Красивее как-то можно?#
В JSX – можно:
const divStyle = {
color: 'white',
backgroundColor: 'cyan',
};
export function HelloStylesComponent() {
return <div style={divStyle}>Hello React Styles!div>;
}
Минусы, в целом, всё те же. Но тем не менее, в 2014 году об этом рассказали на одной из первых посвящённых реакту конференций, пригласив сообщество к дискуссии.
CSS-in-JS#
Подход CJS предполагает, что описанные вами стили – неважно, будут они реализованы на CSS-подобном языке или через JS-объекты – применятся к вашим элементам через обычные классы, которые будут автоматически выстраиваться в нужную вам композицию. Вы получаете всё, что имели в CSS, плюс динамические возможности JavaScript.
В очень грубом приближении, при выполнении или сборке вашего кода будут автоматически созданы классы HTML-элементов и создастся тег и/или CSS-файл со всеми нужными правилами.
Плюсы:
- Соблюдается принцип единой ответственности: компонент полностью отвечает за свой внешний вид и поведение
- Самоудаление «мёртвого» (неиспользуемого) кода
- Динамические возможности ограничены лишь вашей готовностью их контролировать
- Никаких проблем с именованием классов
- Контроль состояния в любой момент
- Изоляция стилей
Минусы:
- Производительность ниже, чем у классического подхода
Верно не всегда, библиотеки вроде linaria генерируют отдельный CSS-файл, контролируемый пользовательскими свойствами, рассмотрим позже - Требуется некоторое время на привыкание
Первое что приходит в голову – синтаксис, второе – сам подход - Лучшие возможности требуют дополнительной настройки сборки
Упрощение отладки, объединение и атомизация классов - Сложности с поддержкой редакторами кода
Верно не всегда, разве только вы не используете что-то редкое - Стили не применятся, пока не выполнится JS
Верно не всегда, см. первый пункт - Очевидно, стили кешироваться отдельно от скриптов не смогут
Также см. первый пункт
Кстати, в CJS возможна автоматическая атомизация классов, что позволяет сжать ваш код до предела. Представьте себе, что все написанные вами правила сгруппируются и выстроятся в ровную композицию. Без Tailwind CSS и прочих подходов из нулевых (вы правда думали, что Tailwind первый такой? Поглядите на Tachyons, я даже ссылку давать не хочу). Отлаживать это, конечно, будет непросто, зато размер сборки минимальный и скорость максимальна. Если интересен результат такого подхода – взгляните на недавний редизайн Facebook.
Давайте пройдёмся по примерам из нескольких популярных библиотек.
Что в наличии?#
JSS#
Само понятие CSS-in-JS узурпировал (создал?) JSS. Первый известный релиз на GitHub датируется 2014 годом сразу с версии 0.2.1, можно предположить, что сама идея зародилась и раньше. Если вы хоть раз использовали Material UI, вы использовали и JSS. Наверняка не все пытались переопределить встроенные стили их компонентов (если интересно, как-нибудь расскажу), потому давайте взглянем на пример, взят из документации с упрощениями:
import jss from 'jss';
import color from 'color';
const styles = {
'@global': {
body: {
color: 'green',
},
},
button: {
'&:hover': {
background: 'blue',
},
},
ctaButton: {
extend: 'button',
'&:hover': {
background: color('blue').darken(0.3).hex(),
},
},
'@media (min-width: 1024px)': {
button: {
width: 200,
},
},
};
const { classes } = jss.createStyleSheet(styles).attach();
document.body.innerHTML = `
">Button
">CTA Button
`;
Обратили внимание? Тут React отсутствует, подход применим к любой реализации. Библиотек CSS-in-JS, не завязанных на React, гораздо больше одной, но, как уже было упомянуто, React и JSX в данном цикле статей применены лишь для собственного удобства автора.
Ещё мне нравится логотип JSS, он максимально издевательский:

Библиотека до сих пор развивается, существует огромное количество плагинов, можно даже, используя теговые шаблоны писать обычный CSS. А можно, используя jss-nested
, обращаться к вложенным компонентам. Но вообще...
Это всё больше похоже на какую-то дикую смесь, чем на CSS. Тут вам на одном уровне даются простые свойства объекта, которые можно переиспользовать и расширять, псевдоклассы/элементы, at-правила, селекторы... Честно говоря, выглядит это всё довольно жутковато. Но многим понравилось (многие JS-программисты в CSS не умеют, простите мне эту вольность), а недостатки и условная сложность JSS открыли дорогу иным решениям. Кстати, всё в том же Material UI рассматривают возможность замены JSS на Styled Components.
jsxstyle#
Появился в 2015 году специально для React (и Preact). Предоставляет набор базовых компонентов, имеющих стили по-умолчанию, и позволяющий их расширять передавая свойства (props) прямо в этот компонент.
Компонент | Стили |
---|---|
Block | display: block; |
Inline | display: inline; |
InlineBlock | display: inline-block; |
Row | display: flex; flex-direction: row; |
Col | display: flex; flex-direction: column; |
InlineRow | display: inline-flex; flex-direction: row; |
InlineCol | display: inline-flex; flex-direction: column; |
Grid | display: grid; |
Box | Без стилей |
Работает это всё как-то так:
<Row alignItems="center" padding={15}>
<Block
backgroundColor="#EEE"
borderRadius={5}
height={320}
width={200}
backgroundSize="contain"
backgroundImage="url(https://i.ytimg.com/vi/j83eF9GHRcM/hq720.jpg)"
/>
<Col fontFamily="sans-serif" fontSize={16} lineHeight="24px">
<Block fontWeight={600}>ПростоBlock>
<Block fontStyle="italic">РазработкаBlock>
Col>
Row>
Ну и свои компоненты уже создаются на их базе:
const RedBlock = (props) => <Block {...props} color="red" />;
Чтобы указать имя тега, впрочем, тоже нужно передать свойство – component
. Минусы всё те же, что у JSS: нужно запоминать громоздкий выдуманный API, стили лезут в логику.
Radium#
2014-2016 года выдались щедрыми на различные новые подходы. Вот и Radium не отставал. Что они сделали? Они переопределили атрибут style таким образом, чтобы он принимал на вход массив. Вы передаёте в него наборы стилей, а они – применяются в строгом порядке:
const styles = {
base: {
background: 'blue',
borderRadius: 4,
color: 'white',
':hover': {
backgroundColor: 'red',
},
},
block: {
display: 'block',
'@media (min-width: 320px)': {
width: '100%',
},
},
};
export function HelloRadiumButton() {
return (
<button style={[styles.base, this.props.block && styles.block]}>
{this.props.children}
button>
);
}
Я намеренно не показываю более сложные примеры, это в общем не имеет смысла. Тем более проект Radium официально закрыт. Хотя всё же покажу один. Используя метод Radium.getState
можно создать новый элемент основываясь на стиле другого. Например, создать и показать подсказку при наведении на кнопку:
return (
<div>
<button key="keyForButton" style={[styles.button]}>
Hover me!
button>
{Radium.getState(this.state, 'keyForButton', ':hover') ? (
<span> Hovering!span>
) : null}
div>
);
Надо только не забыть создать определение стилей для :hover
, даже пустое.
Styled Components#
В разделе о JSS я уже заикнулся о теговых шаблонах. Посмотрите вот на это:
const Button = styled.button`
background-color: papayawhip;
border-radius: 3px;
color: palevioletred;
`;
Если вы прочитали документацию по теговым шаблонам, вы уже в курсе, что код выше – это вполне себе стандарт языка. Мы вызываем функцию, на вход которой подаём строку. И эта строка у нас в примере – некоторый набор CSS-правил, которые можно разобрать и получить те же объекты с правилами, что и в других решениях, после чего выдать имя класса.
Казалось бы, на этом можно остановиться, но создатели Styled Components решили пойти дальше. Они решили из каждой такой записи генерировать новый компонент. Само название уже на это намекает. Идея проще некуда, но последствия неоценимы.
Да, вы не ошиблись. Вот та вот короткая запись выше – это самый настоящий React-компонент, готовый к использованию. Он принимает на вход все поддерживаемые кнопками атрибуты HTML, даже class
(className
) и style
. А раз это полноценный компонент, наверное и свойства (props
) можно передать? Ну да, естественно:
const Button = styled.button`
background-color: ${(props) => props.bgColor || 'cyan'};
border-radius: 3px;
color: lavender;
`;
export function HelloStyledComponent() {
return <Button bgColor="red">Hello!Button>;
}
Вы уже, наверное, догадались, что ещё можно сделать, но я оставлю это на другой раз. Пока же важно знать, что Styled Components породили огромное число подражателей. Какие-то из них сильно быстрее SC, какие-то дают ещё больше возможностей и даже могут генерировать CSS-файлы во время сборки, убирая все возможные проблемы с производительностью на корню. Но SC всё ещё живы и процветают, вбирая в себя лучшие идеи.
Хардкор напоследок#
Как я уже сказал, 2014-2016 года были щедры на идеи. Можно было даже на уровне транспилятора всё это решить. Встречайте, babel-plugin-css-in-js. Распыляться не буду, смотрим. Вход:
const styles = cssInJS({
button: {
padding: 5,
backgroundColor: 'blue',
},
});
<Button className={styles.button} />;
Выход, JavaScript:
const styles = {
button: 'example_js_styles_button',
};
<Button className={styles.button} />;
CSS:
.example_js_styles_button {
padding: 5px;
background-color: blue;
}
Самое прекрасное, это всё происходит на этапе сборки и попадает в отдельный файл. Без накладных расходов. Штука интересная, но никаких динамических возможностей не предоставляет.
Итого#
Как видим, существует множество CSS-in-JS фреймворков и библиотек, но лишь одна из них, благодаря своей потрясающе простой идее, создала вокруг себя некий культ. И это – Styled Components. Вся идея укладывается в названии, а число подражателей и последователей – весьма велико: Emotion, astroturf, linaria. В следующей статье познакомимся со Styled Components поближе.