@extend

При разработке страницы часто возникают случаи, когда один класс должен иметь все стили другого класса, а также свои собственные специфические стили. Например, методология БЭМ поощряет классы-модификаторы, которые относятся к тем же элементам, что и классы блоков или элементов. Но это может создать загроможденный HTML, склонный к ошибкам из-за того, что забыли включить оба класса, и может внести несемантические проблемы стиля в вашу разметку.

<div class="error error--serious">
  Oh no! You've been hacked!
</div>
 
.error {
  border: 1px #f00;
  background-color: #fdd;
}

.error--serious {
  border-width: 3px;
}

Правило Sass @extend решает эту проблему. Он написан @extend <selector> и сообщает Sass, что один селектор должен наследовать стили другого.

SCSS Syntax

.error {
  border: 1px #f00;
  background-color: #fdd;

  &--serious {
    @extend .error;
    border-width: 3px;
  }
}

Когда один класс расширяет другой, Sass стилизует все элементы, которые соответствуют расширителю, как если бы они также соответствовали расширяемому классу. Когда один селектор класса расширяет другой, он работает точно так же, как если бы вы добавили расширенный класс к каждому элементу в вашем HTML, который уже имел расширяющийся класс. Вы можете просто написать class="error--serious", и Sass позаботится о том, чтобы он был оформлен так, как если бы он также имел class="error".

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

SCSS Syntax

.error:hover {
  background-color: #fee;
}

.error--serious {
  @extend .error;
  border-width: 3px;
}

⚠️ Внимание!

Расширения разрешаются после компиляции остальной части вашей таблицы стилей. В частности, это происходит после разрешения родительских селекторов. Это означает, что если вы используете @extend .error, это не повлияет на внутренний селектор в .error { &__icon { ... } }. Это также означает, что родительские селекторы в SassScript не могут видеть результаты расширения.

Как это работает permalinkКак это работает

В отличие от миксин, которые копируют стили в текущее правило стиля, @extend обновляет правила стиля, содержащие расширенный селектор, так что они также содержат расширяющий селектор. При расширении селекторов Sass выполняет интеллектуальную унификацию:

  • Он никогда не генерирует селекторы вроде #main#footer, которые не могут соответствовать никаким элементам.

  • Это гарантирует, что сложные селекторы чередуются, так что они работают независимо от того, в каком порядке вложены элементы HTML.

  • Он максимально сокращает избыточные селекторы, при этом гарантируя, что специфичность больше или равна специфичности расширителя.

  • Он знает, когда один селектор совпадает со всем, что делает другой, и может комбинировать их вместе.

  • Он разумно обрабатывает комбинаторы, универсальные селекторы и псевдоклассы, содержащие селекторы.

SCSS Syntax

.content nav.sidebar {
  @extend .info;
}

// Это не будет расширено, потому что `p` несовместимо с `nav`.
p.info {
  background-color: #dee9fc;
}

// Невозможно узнать, будет ли `<div class="guide">` внутри или снаружи
// `<div class="content">`, поэтому Sass генерирует оба, чтобы быть в безопасности.
.guide .info {
  border: 1px solid rgba(#000, 0.8);
  border-radius: 2px;
}

// Sass знает, что каждый элемент, соответствующий "main.content", также соответствует
// ".content" и избегает создания ненужных чередующихся селекторов.
main.content .info {
  font-size: 0.8em;
}

💡 Интересный факт:

Вы можете напрямую получить доступ к интеллектуальной унификации Sass, используя функции выбора! Функция selector.unify() возвращает селектор, который соответствует пересечению двух селекторов, в то время как функция selector.extend() работает так же, как @extend, но с одним селектором.

⚠️ Внимание!

Поскольку @extend обновляет правила стиля, которые содержат расширенный селектор, их стили имеют приоритет в каскаде в зависимости от того, где появляются правила стиля расширенного селектора, а не в зависимости от того, где появляется @extend. Это может сбивать с толку, но помните: это тот же приоритет, который имели бы эти правила, если бы вы добавили расширенный класс в свой HTML!

Селекторы заполнителей permalinkСелекторы заполнителей

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

SCSS Syntax

.alert:hover, %strong-alert {
  font-weight: bold;
}

%strong-alert:hover {
  color: red;
}

Частные заполнители permalinkЧастные заполнители

Как и участники модуля, селектор-заполнитель можно пометить как частный, начав его имя с - или _. Селектор частного заполнителя может быть расширен только внутри таблицы стилей, которая его определяет. Для любых других таблиц стилей это будет выглядеть так, как будто этого селектора не существует.

Область расширения permalinkОбласть расширения

Когда одна таблица стилей расширяет селектор, это расширение будет влиять только на правила стиля, написанные в восходящих модулях, то есть на модули, которые загружаются этой таблицей стилей с помощью правила @use или правилоа @forward, модули, загруженные этими модулями, и так далее. Это помогает сделать ваши правила @extend более предсказуемыми, гарантируя, что они влияют только на стили, о которых вы знали, когда их писали.

⚠️ Внимание!

Расширения вообще не имеют области видимости, если вы используете правило @import. Они не только повлияют на каждую импортируемую вами таблицу стилей, но и повлияют на каждую таблицу стилей, которая импортирует вашу таблицу стилей, все остальное, что эти таблицы стилей импортируют, и так далее. Без @use, расширения будут глобальными.

Обязательные и необязательные расширения permalinkОбязательные и необязательные расширения

Обычно, если @extend не соответствует ни одному селектору в таблице стилей, Sass выдаст ошибку. Это помогает защитить от опечаток или переименования селектора без переименования селекторов, которые от него наследуются. Расширения, требующие наличия расширенного селектора, являются обязательными.

Однако это не всегда может быть тем, что вам нужно. Если вы хотите, чтобы @extend не выполнял никаких действий, если расширенный селектор не существует, просто добавьте в конец !optional.

Расширения или Миксины? permalinkРасширения или Миксины?

Расширения и примеси - это способы инкапсуляции и повторного использования стилей в Sass, что, естественно, поднимает вопрос о том, когда какой из них использовать. Миксины, очевидно, необходимы, когда вам нужно настроить стили с помощью аргументов, но что, если они всего лишь фрагменты стилей?

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

💡 Интересный факт:

Большинство веб-серверов сжимают обслуживаемый ими CSS, используя алгоритм, который очень хорошо обрабатывает повторяющиеся фрагменты идентичного текста. Это означает, что, хотя миксины могут создавать больше CSS, чем расширять, они, вероятно, не существенно увеличат объем, необходимый вашим пользователям для загрузки. Так что выбирайте ту функцию, которая больше всего подходит для вашего варианта использования, а не ту, которая генерирует меньше всего CSS!

Ограничения permalinkОграничения

Запрещенные селекторы permalinkЗапрещенные селекторы

Совместимость (No Compound Extensions):
Dart Sass
LibSass
Ruby Sass

Только простые селекторы - отдельные селекторы, такие как .info или a могут быть расширены. Если бы .message.info мог быть расширен, определение @extend говорит, что элементы, соответствующие расширителю, будут иметь такой стиль, как если бы они соответствовали .message.info. Это то же самое, что и сопоставление .message и .info, поэтому писать это вместо @extend .message, .info не принесет никакой пользы.

Точно так же, если бы .main .info можно было расширить, он бы делал (почти) то же самое, что и расширение .info самостоятельно. Тонкие различия не стоят того, чтобы выглядеть так, будто он делает что-то существенно другое, так что это тоже недопустимо.

SCSS Syntax

.alert {
  @extend .message.info;
  //      ^^^^^^^^^^^^^
  // Error: Write @extend .message, .info instead.

  @extend .main .info;
  //      ^^^^^^^^^^^
  // Error: write @extend .info instead.
}

HTML-эвристика permalinkHTML-эвристика

Когда @extend чередует сложные селекторы, он не генерирует все возможные комбинации селекторов предков. Многие из селекторов, которые он мог бы сгенерировать, вряд ли действительно будут соответствовать реальному HTML, и создание их всех сделало бы таблицы стилей слишком большими для очень небольшой реальной ценности. Вместо этого он использует эвристику: он предполагает, что предки каждого селектора будут самодостаточными, без чередования с предками других селекторов.

SCSS Syntax

header .warning li {
  font-weight: bold;
}

aside .notice dd {
  // Sass не генерирует CSS для соответствия <dd> в
  //
  // <header>
  //   <aside>
  //     <div class="warning">
  //       <div class="notice">
  //         <dd>...</dd>
  //       </div>
  //     </div>
  //   </aside>
  // </header>
  //
  // потому что сопоставление всех таких элементов потребовало бы,
  // чтобы мы сгенерировали девять новых селекторов вместо двух.
  @extend li;
}

Расширить в @media permalinkРасширить в @media

Хотя @extend разрешен в @media и других at-правилах CSS, не разрешено расширять селекторы, которые появляются вне его at-правила. Это связано с тем, что расширяемый селектор применяется только в пределах данного медиа-контекста, и нет способа убедиться, что ограничение сохраняется в сгенерированном селекторе без дублирования всего правила стиля.

SCSS Syntax

@media screen and (max-width: 600px) {
  .error--serious {
    @extend .error;
    //      ^^^^^^
    // Error: ".error" was extended in @media, but used outside it.
  }
}

.error {
  border: 1px #f00;
  background-color: #fdd;
}