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) | Можно хранить стили рядом с остальным кодом в одних файлах |
Для комфортной работы со стилями приходится настраивать ряд дополнительных инструментов | Не нужно настраивать множество дополнительных инструментов для комфортной работы со стилями |