@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 Syntax

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

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


CSS Output

.error, .error--serious {
  border: 1px #f00;
  background-color: #fdd;
}
.error--serious {
  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;
}

Sass Syntax

.error:hover
  background-color: #fee


.error--serious
  @extend .error
  border-width: 3px

CSS Output

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

.error--serious {
  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 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

CSS Output

p.info {
  background-color: #dee9fc;
}

.guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar {
  border: 1px solid rgba(0, 0, 0, 0.8);
  border-radius: 2px;
}

main.content .info, main.content nav.sidebar {
  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;
}

Sass Syntax

.alert:hover, %strong-alert
  font-weight: bold


%strong-alert:hover 
  color: red

CSS Output

.alert:hover {
  font-weight: bold;
}




Частные заполнители 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

LibSass и Ruby Sass в настоящее время позволяют расширять составные селекторы, такие как .message.info. Однако это поведение не соответствует определению @extend: вместо стилизованных элементов, которые соответствуют расширяющему селектору, как если бы он имел class="message info", на который бы повлияли правила стиля, которые включали либо .message или .info, он стилизовал их только с помощью правил, включающих как .message и info.

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

Смотрите страницу критических изменений для получения более подробной информации.

Только простые селекторы - отдельные селекторы, такие как .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.
}

Sass 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;
}

Sass 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

CSS Output

header .warning li, header .warning aside .notice dd, aside .notice header .warning dd {
  font-weight: bold;
}


















Расширить в @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;
}

Sass 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