Статьи

Остерегайтесь гнездования селекторов в Sass

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

Некоторые согласились, некоторые нет. В любом случае, это вызвало пару интересных мыслей, поэтому я подумал, что соберу статью, чтобы поговорить на эту тему.

Что такое вложенность селектора?

Вложение селекторов — это особенность препроцессоров CSS, позволяющая авторам вкладывать селекторы в селекторы для создания ярлыков. Например:

.parent { color: red; .child { color: blue; } } 

Который будет компилировать в следующий CSS:

 .parent { color: red; } .parent .child { color: blue; } 

В этом примере .child вложен в .parent , чтобы избежать повторения родительского селектора.

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

Что не так с вложенностью?

Само по себе нет ничего плохого во вложении. Эта функция имеет смысл. Проблема, как это часто бывает, заключается не в функции, а в том, как мы ее используем. Позвольте мне начать с пары примеров, на которые я наткнулся.

Вот этот от Мики Годболт :

 .tabs { .tab { background: red; &:hover { background: white; } .tab-link { color: white; @at-root #{selector-replace(&, '.tab', '.tab:hover')} { color: red; } } } } 

Или этот от ZI Цю :

 .root { width: 400px; margin: 0 auto; .links { .link { display: inline-block; & ~ .link { margin-left: 10px; } a { padding: 10px 40px; cursor: pointer; background: gray; &:hover { background: blue; color: white; font-size: 700; } .icon { margin-right: 5px; @include selector-modifier(-2 ':hover', 1 suffix '.zh') { color: red; background: green; } @include selector-modifier(-2 ':hover', 1 suffix '.en') { color: yellow; background: green; } } } } } } 

Давайте начнем с отказа от ответственности: это умный код. Я ни в коем случае не говорю, что это плохой код. Я предполагаю, что этот код делает именно то, для чего предназначен.

А что если я спросил вас, что именно эти два примера предназначены для этого? Основываясь на первом взгляде, не тратя пару минут на просмотр кода, сможете ли вы догадаться?

И я нет. Потому что это сложно.

Вложенность усложняет код

В обоих приведенных выше примерах используются некоторые функции селектора (из Sass 3.4 ), чтобы частично переписать текущий контекст селектора ( & ).

Так что, если я не ошибаюсь, оба примера включают в себя больше кода, чтобы писать меньше кода, и все это в дополнение к некоторой дополнительной сложности. Как насчет написания простого кода с самого начала?

Это заявление, которое я уже сделал ранее: функции селектора не предназначены для повседневного использования. Я полагаю, что Крис Эппштейн и Натали Вайзенбаум прямо заявили, что они по существу добавляют эту функцию, чтобы помочь разработчикам фреймворка.

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

Селектор ссылок неоднозначен

Селектор ссылок в Sass ( & ) иногда может быть весьма неоднозначным. В зависимости от того, как он используется, он может дать совершенно другой вывод CSS. Вот коллекция простых примеров.

 /* SCSS */ .element { &:hover { color: red; } } /* CSS */ .element:hover { color: red; } 
 /* SCSS */ .element { &hover { color: red; } } /* CSS */ .elementhover { color: red; } 
 /* SCSS */ .element { & .hover { color: red; } } /* CSS */ .element .hover { color: red; } 
 /* SCSS */ .element { &-hover { color: red; } } /* CSS */ .element-hover { color: red; } 
 /* SCSS */ .element { &.hover { color: red; } } /* CSS */ .element.hover { color: red; } 
 /* SCSS */ .element { .hover& { color: red; } } /* Syntax Error */ Invalid CSS after ".hover": expected "{", was "&" "&" may only be used at the beginning of a compound selector. 
 /* SCSS */ .element { &:hover & { color: red; } } /* CSS */ .element:hover .element { color: red; } 
 /* SCSS */ .element { &:hover { & { color: red; } } } /* CSS */ .element:hover { color: red; } 

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

Дело в том, что некоторые операции работают, некоторые — нет (обратите внимание на ошибку, отображаемую в одном из примеров выше). Некоторые генерируют составной селектор, некоторые нет. В зависимости от того, что делается, и предыстории Sass следующего разработчика, который будет использовать код, может быть довольно сложно отлаживать подобные вещи.

Неисследуемые селекторы

На данный момент я в основном придираюсь. Но есть кое-что, что мне не нравится, когда Sass-код широко использует вложенность для создания BEM-подобных селекторов:

 .block { /* Some CSS declarations */ &--modifier { /* Some CSS declarations for the modifier */ } &__element { /* Some CSS for the element */ &--modifier { /* Some CSS for the modifier of the element */ } } } 

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

 .block { /* Some CSS declarations */ } .block--modifier { /* Some CSS declarations for the modifier */ } .block__element { /* Some CSS for the element */ } .block__element--modifier { /* Some CSS for the modifier of the element */ } 

С одной стороны, это позволяет избежать повторения .block в каждом селекторе, что может быть .user-profile когда у вас есть имена блоков, такие как .user-profile .

С другой стороны, он создает новые селекторы из ниоткуда, которые невозможно найти. Что если разработчик захочет найти CSS из .block__element ? Скорее всего, он попытается найти его в проекте из своей IDE, не найдя ничего, потому что этот селектор никогда не создавался как есть.

На самом деле, похоже, я не единственный, кто придерживается этой точки зрения. Келиг, который в то время работал в The Guardian, написал в твиттере:

Кроме того, я думаю, что лучше повторять базовое имя. Таким образом, кристально ясно, что происходит.

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

Когда можно использовать вложение?

До тех пор, пока вы и ваша команда чувствуете себя хорошо с дополнительной сложностью, которую это может повлечь за собой, всегда хорошо использовать вложение селекторов. Если с этим все в порядке, то это круто!

Если вы спросите меня сейчас, я чувствую, что добавление псевдоклассов и псевдоэлементов — практически единственный случай, когда это нормально. Например:

 .element { /* Some CSS declarations */ &:hover, &:focus { /* More CSS declarations for hover/focus state */ } &::before { /* Some CSS declarations for before pseudo-element */ } } 

Это лучший вариант использования для вложения селекторов, который я могу себе представить. Он не только избегает повторения одного и того же селектора, но также охватывает все элементы этого элемента (состояния и виртуальные дочерние элементы) в одном и том же наборе правил CSS. Кроме того, & не является двусмысленным: это означает .element , не больше, не меньше.

Другой случай, когда я думаю, что вложение интересно, когда вы хотите применить некоторые пользовательские стили к простому селектору на основе верхнего контекста. Например, при использовании CSS-хуков Modernizr:

 .element { /* Some CSS declarations */ .no-csstransforms & { /* Some CSS declarations when CSS transforms are not supported */ } } 

В этом сценарии я чувствую, что это достаточно ясно, что .no-csstransforms & предназначен для контекстуализации текущего селектора всякий раз, когда CSS-преобразования не поддерживаются. Хотя это может быть на грани того, что я считаю приемлемым.

Каковы мои рекомендации?

Напишите простой CSS. Давайте возьмем пример Мики, который немного сложен, но не настолько сложен, что было бы больно переписывать.

Это текущий код, очевидно предназначенный для стилизации некоторых вкладок:

 .tabs { overflow: hidden; .tab { background: red; &:hover { background: white; } .tab-link { color: white; @at-root #{selector-replace(&, '.tab', '.tab:hover')} { color: red; } } } } 

Вместо этого мы могли бы написать это так:

 .tabs { overflow: hidden; } .tab { background: red; &:hover { background: white; } } .tab-link { color: white; .tab:hover & { color: red; } } 

Я не могу придумать единственной причины, по которой кто-то предпочитает первый фрагмент кода. Он не только длиннее, но и менее явен и использует функции Sass, которые не обязательно известны всем разработчикам.

Обратите внимание, что вывод CSS не совсем тот же, потому что мы также упростили код. Вместо того, чтобы иметь селекторы до тех пор, пока .tabs .tab .tab-link , мы перешли к более простым вещам, таким как .tab-link .

На данный момент это не столько вложенность селекторов, сколько соглашения об именах и методология селекторов. Например, при использовании BEM вы называете вещи в соответствии с тем, что они есть, а не с тем, где они находятся, что часто приводит к простым селекторам (здесь простой означает не составной ), что означает отсутствие вложенности.

В соответствии с Руководством по CSS от Гарри Робертса:

При написании CSS важно правильно распределять наши селекторы и выбирать правильные вещи по правильным причинам […] Учитывая постоянно меняющийся характер большинства проектов пользовательского интерфейса и переход к более компонентно-ориентированным архитектурам, в наших интересах не стилизовать вещи, основываясь на том, где они находятся, а на том, что они есть.

Хорошее эмпирическое правило, когда дело доходит до селекторов CSS, чем короче, тем лучше . Мало того, что лучше на любом уровне (производительность, простота, переносимость, намерение и т. Д.), Но оказывается, что гораздо проще не испортить вложенность, когда селекторы короткие.

Последние мысли

Всякий раз, когда я говорю, что я думаю, что вложение селекторов не такая уж хорошая идея, люди всегда говорят мне: «У меня никогда не было проблем», «мы используем это каждый день, нет проблем», «это потому, что вы делаете сумасшедшие вещи с этим ». Конечно, нет ничего плохого в этой функции.

Препроцессоры не выводят плохой код, плохие разработчики делают. И здесь речь идет даже не о выходе, а о входе. Код становится все менее читаемым, когда мы добавляем дополнительные уровни сложности. Вложенные селекторы — один из тех слоев.

Нам дали вложенность, поэтому мы злоупотребили его силой. Затем нам дали селекторные функции, поэтому мы использовали их для того, чтобы исправить неразбериху, которую мы сделали с вложением. Это не правильно.

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

Эта статья была переведена на французский язык на La Cascade . Мерси, Пьер!