@extend

在设计页面时,经常会遇到一个类应该具有另一个类的所有样式,同时还有自己特定的样式的情况。例如,BEM 方法论鼓励在块或元素类的同一元素上使用修饰符类。但这可能会导致 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 <选择器>,告诉 Sass 一个选择器应该继承另一个选择器的样式。

Playground

SCSS Syntax

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

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

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 知道要在所有使用该选择器的地方进行扩展。这确保了你的元素的样式完全符合匹配被扩展选择器的样式。

Playground

SCSS Syntax

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

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

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

⚠️ Heads up!

扩展是在样式表的其余部分编译后解析的。特别是,它发生在父选择器解析之后。这意味着如果你 @extend .error,它不会影响 .error { &__icon { ... } } 中的内部选择器。这也意味着 SassScript 中的父选择器无法看到扩展的结果。

工作原理工作原理 permalink

混入复制样式到当前样式规则不同,@extend 更新包含被扩展选择器的样式规则,使其也包含扩展选择器。在扩展选择器时,Sass 进行智能统一

  • 它永远不会生成像 #main#footer 这样不可能匹配任何元素的选择器。

  • 它确保复杂的选择器交错,以便无论 HTML 元素的嵌套顺序如何都能正常工作。

  • 它尽可能修剪冗余选择器,同时确保特异性大于或等于扩展类的特异性。

  • 它知道当一个选择器匹配另一个选择器的所有内容时,可以将它们组合在一起。

  • 它智能地处理组合器通用选择器包含选择器的伪类

Playground

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

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







💡 Fun fact:

你可以直接使用 选择器函数访问 Sass 的智能统一!selector.unify() 函数返回匹配两个选择器交集的选择器,而 selector.extend() 函数的工作方式与 @extend 完全相同,但作用于单个选择器。

⚠️ Heads up!

因为 @extend 更新包含被扩展选择器的样式规则,它们在级联中的优先级是基于被扩展选择器的样式规则出现的位置,而不是基于 @extend 出现的位置。这可能会造成混淆,但请记住:这与你在 HTML 中添加被扩展类的规则优先级相同!

占位符选择器占位符选择器 permalink

有时你希望编写一个用于扩展的样式规则。在这种情况下,你可以使用占位符选择器,它们看起来像以 % 而不是 . 开头的类选择器。任何包含占位符的选择器都不会包含在 CSS 输出中,但扩展它们的选择器会被包含。

Playground

SCSS Syntax

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

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

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 规则更可预测,确保它们只影响你编写时意识到的样式。

⚠️ Heads up!

如果你使用 @import 规则,扩展将不受任何作用域限制。它们不仅会影响你导入的每个样式表,还会影响导入你的样式表的每个样式表、这些样式表导入的所有内容,以此类推。没有 @use,扩展是全局的。

强制和可选扩展强制和可选扩展 permalink

通常,如果 @extend 没有匹配任何样式表中的选择器,Sass 会产生一个错误。这有助于防止拼写错误或在重命名选择器时忘记重命名继承自它的选择器。需要被扩展的选择器存在的扩展是强制的。

但这可能并不总是你想要的。如果你希望 @extend 在被扩展的选择器不存在时什么也不做,只需在末尾添加 !optional

扩展还是混入?扩展还是混入? permalink

扩展和混入都是 Sass 中封装和重用样式的方式,这自然会引发何时使用哪一种的问题。当你需要使用参数配置样式时,混入显然是必要的,但如果只是一块样式呢?

作为一个经验法则,当你表达语义类(或其他语义选择器)之间的关系时,扩展是最佳选择。因为带有 .error--serious 类的元素是一个错误,所以它扩展 .error 是有意义的。但对于非语义的样式集合,编写混入可以避免级联的麻烦,并使后续配置更容易。

💡 Fun fact:

大多数 Web 服务器使用一种非常擅长处理重复的相同文本块的算法来压缩它们提供的 CSS。这意味着,尽管混入可能会生成比扩展更多的 CSS,但它们可能不会实质性地增加用户需要下载的内容。所以选择最适合你使用场景的特性,而不是生成最少 CSS 的特性!

限制限制 permalink

不允许的选择器不允许的选择器 permalink

Compatibility (No Compound Extensions):
Dart Sass
LibSass
Ruby Sass

LibSass 和 Ruby Sass 目前允许扩展像 .message.info 这样的复合选择器。然而,这种行为与 @extend 的定义不符:它不是将元素样式化为好像有 class="message info",这将受到包含 .message .info 的样式规则影响,而是只使用同时包含 .message .info 的规则进行样式化。

为了保持 @extend 的定义简单明了,并保持实现的清晰和高效,这种行为现在已被弃用,并将从未来版本中移除。

更多详情请参见破坏性变更页面

只有简单选择器——像 .infoa 这样的单个选择器可以被扩展。如果 .message.info 可以被扩展,@extend 的定义表明匹配扩展类的元素将被样式化,就像它们匹配 .message.info 一样。这与匹配 .message.info 是一样的,所以写这个而不是 @extend .message, .info 没有任何好处。

同样,如果 .main .info 可以被扩展,它(几乎)会做与单独扩展 .info 相同的事情。微妙的差异不值得看起来像做了实质性不同的事情,所以这也是不允许的。

Playground

SCSS Syntax

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

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

Sass Syntax

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

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

HTML 启发式HTML 启发式 permalink

@extend 交错复杂选择器时,它不会生成所有可能的祖先选择器组合。它可能生成的许多选择器不太可能实际匹配真实的 HTML,生成所有这些选择器会使样式表变得太大,而实际价值很小。相反,它使用启发式:假设每个选择器的祖先是自包含的,不会与任何其他选择器的祖先交错。

Playground

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

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 中的扩展@media 中的扩展 permalink

虽然在 @media 和其他 CSS at-rules 中允许 @extend,但不允许扩展出现在其 at-rule 之外的选择器。这是因为扩展选择器只在给定的媒体上下文中应用,并且没有办法在不复制整个样式规则的情况下确保这个限制在生成的选择器中得到保留。

Playground

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

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