CSS-in-JS — новый подход к стилизации компонентных веб-приложений. Он помогает преодолеть ряд ограничений и проблем традиционных методов работы с CSS.

Посмотрим, какие проблемы есть в традиционном CSS, и как CSS-in-JS их решает.

Проблемы при сложной стилизации

Представим, что нам нужно создать кнопку, которая:

  • имеет два стиля, filled с заливкой и outlined с обводкой;
  • имеет два размера, medium (средняя) и large (большая);
  • представлена в двух цветах, зелёном и оранжевом;
  • может не иметь иконки, а может и иметь.

Этот компонент выглядит примерно так:

function Button(props) {
  const className = classNames('button', {
    'm-variant-filled': props.variant === 'filled',
    'm-variant-outlined': props.variant === 'outlined',
    'm-size-medium': props.size === 'medium',
    ...
  });

  return <button className={className}>{props.children}</button>;
}

То есть как набор классов-модификаторов, логика управления которыми смешана с основной логикой. При более комплексных компонентах это может значительно усложнить работу.

При помощи CSS-in-JS задача решается в более наглядном формате:

const StyledButton = styled.button`
  border-radius: 4px;
  cursor: pointer;

  ${(props) => props.variant === 'filled' && css`/* стили для варианта filled */`}
  ${(props) => props.variant === 'outlined' && css`/* стили для варианта outlined */`}
  ${(props) => props.size === 'medium' && css`/* стили для размера medium */`}

  ...

  ${(props) => {
    // сложная логика формирования стилей, описанная при помощи JS
    return css`/* стили, которые сложно описать при помощи обычного CSS */`;
  }}
`;

function Button(props) {
  return <StyledButton {...props}>{props.children}</StyledButton>
}

Вся стилизация описана в отдельной сущности — StyledButton. Скругления и курсор есть у всех кнопок без исключений. Остальные стили применяются или не применяются в зависимости от значений пропов, и эта зависимость описана при помощи JavaScript. Основной код компонента (функция Button) остаётся максимально простым.

Компоненты в современных интерфейсах могут иметь множество параметров и состояний. Часто возможностей CSS недостаточно для реализации подобной логики, и приходится придумывать «велосипеды». А подход CSS-in-JS расширяет CSS при помощи JavaScript и помогает реализовать нетривиальную стилизацию в простом и понятном формате.

💫 Изучите CSS-in-JS

Пройдите профессиональный курс и создайте свой сайт, используя популярную комбинацию: React и styled-components.

Неуправляемые глобальные стили

Традиционно все стили приложения работают в едином глобальном пространстве имён, что рано или поздно может приводить к конфликтам. Вы можете возразить и привести в пример всевозможные подходы инкапсуляции стилей, например БЭМ. Да, они работают, но не во всех сценариях.

Представим ситуацию: вы разработали компонент кнопки и стилизовали его при помощи класса .button. CSS-правило с селектором по этому классу работает глобально, все кнопки выглядят хорошо, и ничто не предвещает беды. И тут ваш коллега решил добавить кнопку с другим дизайном и стилизовал её тоже при помощи класса .button. Хорошо, если конфликт стилей обнаружится сразу, но в худшем сценарии все кнопки в проекте могут сломаться.

Подобная проблема может появиться и при подключении сторонней библиотеки компонентов, если кнопки в ней стилизованы при помощи класса .button. Или наоборот, если вы делаете библиотеку компонентов, ваши стили могут конфликтовать со стилями проектов, где используется библиотека.

В CSS-in-JS стили работают на уровне компонентов и тесно связаны с ними при помощи автоматически генерируемых классов. Для демонстрации этой механики отрендерим компонент из примера выше:

render(<Button>Кнопка</Button>);

В результате выполнения кода получим такой результат:

<style>
  .bZkfAO { /* стили кнопки */ }
</style>

<button class="bZkfAO">Кнопка</button>

Стили применяются к компоненту при помощи сгенерированного класса. Такие классы уникальны и не могут дублироваться в вашем или чужом коде, что исключает появление конфликтов в ваших и внешних стилях.

Алгоритм генерации названий классов можно настроить, чтобы сделать их более понятными и описательными.

В нашем примере итоговое CSS-правило с селектором по уникальному классу расположено внутри элемента style на уровне документа. Это самый популярный способ подключения стилей, реализованный во многих инструментах для работы с CSS-in-JS. Но есть и альтернативные решения, о которых мы узнаем ближе к концу курса.

А ещё стили подключаются к документу только при необходимости. Если компонент рендерится, стили есть. Если не рендерится, стилей нет. При удалении кнопки из примера выше CSS-правило пропадает вместе с ней:

<style>
  /* тут были стили кнопки */
</style>

<!-- тут была кнопка -->

То есть CSS-in-JS избавляет от ненужных стилей, что особенно полезно для больших и долгоживущих проектов, которые по мере развития подвергаются повторной стилизации. А ещё он автоматизирует создание критических стилей при серверном рендеринге.

CSS и JS нужно хранить раздельно

Любители Vue.js могут пропустить этот раздел.

Обычно стили компонентов описывают в отдельных файлах. Из-за этого файловая структура проекта становится более громоздкой:

components
├── button
│   ├── button.css
│   └── button.jsx
├── checkbox
│   ├── checkbox.css
│   └── checkbox.jsx
├── input
│   ├── input.css
│   └── input.jsx
...

Такой подход вынуждает использовать дополнительные инструменты для подключения стилей. Это особенно критично, если вы делаете библиотеку — вашим пользователям придётся каждый раз настраивать одни и те же дополнительные инструменты. Для более удобной работы со стилями также нужны препроцессоры, постпроцессоры и автопрефиксер.

CSS-in-JS позволяет хранить стили рядом с основным кодом компонента в одном файле (хотя можно и в разных) и не требует настройки дополнительных инструментов — всё работает «из коробки». И файловая структура проекта получается более компактной:

components
├── button.jsx
├── checkbox.jsx
├── input.jsx
...

Вероятно, это основная причина, почему CSS-in-JS называется именно так.

Сравнение CSS и CSS-in-JS

CSS

CSS-in-JS

Нетривиальная стилизация реализуется сложно

Нетривиальная стилизация реализуется проще при помощи JS

Стили работаю глобально

Стили работают на уровне конкретных компонентов

Сложно выделить критические стили и избавиться от ненужных

Удаление ненужных и выделение критических стилей работают автоматически

Нет возможности хранить стили и остальной код в одних файлах (как минимум в случае React)

Можно хранить стили рядом с остальным кодом в одних файлах

Для комфортной работы со стилями приходится настраивать ряд дополнительных инструментов

Не нужно настраивать множество дополнительных инструментов для комфортной работы со стилями