Допустим, мы разрабатываем библиотеку компонентов.
Допустим, мы используем React.
Допустим, в ней есть компонент кнопки.
Очень условно он будет выглядеть так:
// Button.js
import './Button.css'
const Button = ({ children, className = '' }) => {
const cls = 'Button' + className;
return <button className={cls}>{children}</button>
}
А стили будут выглядеть так:
/* Button.css */
.Button {
background: blue;
padding: 4px 10px;
border: none;
}
Тут к нам приходят разрабы и говорят: "кнопка классная, но нам нужна ссылка в виде кнопки"
Не вопрос! Добавим свойство Component
:
// Button.js
import './Button.css'
const Button = ({ children, Component = 'button', className = '' }) => {
const cls = 'Button' + className;
return <Component className={cls}>{children}</Component>
}
Пример использования:
const linkButton = (
<Button Component="a" href="https://vk.com">
ВКонакте
</Button>
);
Только ой, a
ж является инлайновым элементом. Что такого, спросите вы? Кнопка вроде как выглядела, так и выглядит. А вот нет. Представьте, что пользователи в месте встраивания кнопки хотят добавить ей отступ сверху:
// App.js
<Button className="myButton">Click it!</Button>
/* App.css */
.myButton {
margin-top: 10px;
}
Инлайновый элемент проигнорирует этот margin
.
Ну ничего, и не с такими сложностями мы справлялись. Внесем изменение в стиль кнопки:
/* Button.css */
.Button {
background: blue;
padding: 4px 10px;
border: none;
display: inline-block; /* Зафиксируем display */
}
Но теперь возникла другая беда, куда более подлая и трудноизлечимая. Следите за руками. Представьте себе такой способ использования кнопки в проекте. Допустим, там есть какой-то стейт loading
, который если true
, то кнопку нужно скрыть:
// App.js
<Button className={loading ? 'hidden' : ''} />
/* App.css */
.hidden {
display: none;
}
Вспоминаем, как работает каскад в CSS. Если вдруг забыли, то вот отличная статья, поясняющая за все уровни каскадов. Прочитали? Возвращаемся к нашей беде.
Беда заключается в том, что у .hidden
и .Button
одинаковый вес, при этом оба они претендуют на свойство display
. А значит CSS-парсеру придется выяснять победителя исходя из порядка. Порядок — это последний уровень каскада. Когда ваши CSS-файлы разбиты по модулям, рассчитывать на их определенный порядок появления в итоговом бандле очень некомфортно. В итоге это будет приводить к ситуациям, когда побеждает то один селектор, то другой. Что же делоть...
Layer спешит на помощь
Новый уровень каскада Layer разместился аккурат перед специфичностью и порядком:
Importance
Context
Layer (приветик)
Specificity
Order

Layer позволяет гибко задать разные уровни собственных стилей. В нашем примере таких уровней может быть три (а может быть и больше):
уровень сброса стилей;
уровень стилей библиотеки;
уровень стилей приложения.
Давайте эти уровни зададим.
@layer reset, library; /* Деклалируем приоритет слоёв */
/* Button.css */
@layer reset {
.Button {
display: inline-block;
}
}
@layer library {
.Button {
background: blue;
padding: 4px 10px;
border: none;
}
}
Но постойте, мы вроде хотели три уровня задать, а задали два. Это специфика каскада layer. Всё, что определено за пределами какого-либо уровня, автоматически становится самым приоритетным. На самом деле и library
тут не нужен, но я его оставил для наглядности.
Прикольно, что все стили вне уровней побеждают, так как пользователям не нужно свои перебивающие стили обрамлять в какой-нибудь @layer app
.
То есть вот этот стиль в итоге перебьёт тот, что находится в @layer reset
.
/* App.css */
.hidden {
display: none; /* Наш победитель */
}
Удобно? По-моему это просто фантастика. Во многом потому, что в браузерах этого каскада ещё нет :( пока.