Допустим, мы разрабатываем библиотеку компонентов.
Допустим, мы используем 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-right: 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; /* Наш победитель */
}
Удобно? По моему это просто фантастика. Во многом потому, что в браузерах этого каскада ещё нет :( пока.
Комментарии (13)
wadowad
17.02.2022 11:28Если мы скрываем именно кнопку, то почему бы не использовать
более специфичное сочетание.Button.hidden {}
?ArthurSupertramp Автор
17.02.2022 11:33+1Это норм решение, но мне кажется, что в долгосрочной перспективе такое повышение специфичности — дорога в ад. С каждым следующим витком будет сложнее вносить правки в стили.
efkz
17.02.2022 13:30+1слои это такое же повышение специфичности, только другими методами
ArthurSupertramp Автор
17.02.2022 13:33+1Слои и специфичность — это два разных уровня каскада. Не понимаю, как их можно сравнивать. Слои позволяют декларативно один раз описать приоритет правил вместо бесконечной войны за бОльшую специфичность.
Не претендую на правоту, но способ, который предлагает Layer кажется гораздо более простым, чем определение приоритета на уровне специфичности.
essome
17.02.2022 15:34Эта проблема решена в Angular.
Создаем компонент который не имеет своего тега, а добавляется в виде атрибута
@Component({selector: '[app-button]', template: `<ng-content></ng-content>`}) export class ButtonComponent {}
а потом навешиваем его куда хотим, на кнопку или ссылку
<a href="/" app-button>Link Text</a> <button app-button>Button Text</button>
Для стилей в angular есть :host который привязывается напрямую к компоненту, ему не важно какой тег у него тег
// будет как работать как для a, так и для button :host { display:inline-flex; }
ArthurSupertramp Автор
18.02.2022 07:32Никогда не писал на Angular. Выглядит угарно! А во что в итоге этот код превращается? В первую очередь интересует CSS.
efkz
Вот в этом месте надо было остановиться и дать разрабам хорошего леща за нарушение одного из главных законов пользовательского интерфейса: "Элементы, которые выполняют разные функции, не должны выглядеть одинаково".
В целом, складывается ощущение, что слои - очередная вещь, которая призвана упростить жизнь, но на самом деле всё усложнит.
ArthurSupertramp Автор
Я и согласен, и не согласен. С одной стороны ссылка как будто действительно не должна мимикрировать под кнопку, с другой — это настолько часто встречающийся паттерн почти во всех дизайн системах, что хош не хош приходится его реализовывать.
ArthurSupertramp Автор
Ну и я могу изи привести пример, в котором нет нарушения закона, о котором ты говоришь, но есть описанная проблема. Представь, что ты делаешь компонент заголовка и сбрасываешь ему браузерный margin до нуля. Его так же сложно будет перебить потом пользователям либы.
efkz
Щас вопросики полезут как червячки (с)
1. Зачем сбрасывать браузерный маргин, чтобы потом его заново устанавливать?
2. Почему бы не использовать природную специфичность стилей:
h1{margin:0}
h1.myfancystyle{margin: 1em 0 2em}
#myveryownmodal h1.myfancystyle{margin: 0 0 2em}
3. Почему бы не использовать природный усилитель !important ?
4. Почему бы не использовать style="margin:25vw", если уж припёрло?
ArthurSupertramp Автор
Затем, что допустим большинству нужен
margin
, сброшенный до какого-то значения, а части пользователей требуется его переопределить.Твой
h1
будет аффектить ваще все `<h1 />` на странице. На больших проектах с несколькими независимыми фронтендами такой ресет приводит к проблемам.Использую
!important
в самых патовых ситуациях и не считаю, что он подходит для решения проблемы, которую я описал.Скажу то же самое, что сказал пунктом ранее.
BerkutEagle
Кнопка для перехода на другую страницу сайта. Как сделать "Открыть в новой вкладке" в контекстном меню без использования ссылки?
arsenty
Переход на другую страницу это функционально ссылка. Кнопки про действия на текущей странице.