扩展 @extend

在设计页面时经常会出现这样的情况,一个类应该具有另一个类的所有样式,以及它自己的特定样式。

例如,BEM 方法 鼓励修饰符类在与块或元素类相同的元素上。 但这会导致 HTML 混乱,容易因忘记包含这两个类而出错,并且会在您的标记中带来非语义样式问题。

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

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

Sass 的@extend规则解决了这个问题。 它写成@extend <selector>,它告诉 Sass 一个选择器应该继承另一个选择器的样式。

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

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

当一个类扩展另一个类时,Sass 会为所有匹配扩展器的元素设置样式,就好像它们也匹配被扩展的类一样。 当一个类选择器扩展另一个类选择器时,它的工作方式就像您将扩展类添加到 HTML 中已经具有扩展类的每个元素一样。 你可以只写 class="error--serious",Sass 会确保它的样式就像它也有 class="error" 一样。

当然,选择器不仅仅在样式规则中单独使用。 Sass 知道在使用选择器的_任何地方_进行扩展。 这可确保您的元素的样式与扩展选择器匹配时完全相同。

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

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

⚠️ 注意!

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

怎么运行的

与将样式复制到当前样式规则中的 mixins 不同,@extend 更新包含扩展选择器的样式规则,以便它们也包含扩展选择器。 在扩展选择器时,Sass 会 智能统一

  • 它永远不会生成像 #main#footer 这样不可能匹配任何元素的选择器。
  • 它确保复杂的选择器是交错的,这样无论 HTML 元素的嵌套顺序如何,它们都能正常工作。
  • 它尽可能地修剪冗余选择器,同时仍然确保特异性大于或等于扩展器的特异性。
  • 它知道一个选择器何时匹配另一个选择器所做的一切,并且可以将它们组合在一起。
  • 它智能地处理组合器通用选择器,以及包含选择器的伪类
scss
Scss
scss
.content nav.sidebar {
  @extend .info;
}

// This won't be extended, because `p` is incompatible with `nav`.
p.info {
  background-color: #dee9fc;
}

// There's no way to know whether `<div class="guide">` will be inside or
// outside `<div class="content">`, so Sass generates both to be safe.
.guide .info {
  border: 1px solid rgba(#000, 0.8);
  border-radius: 2px;
}

// Sass knows that every element matching "main.content" also matches ".content"
// and avoids generating unnecessary interleaved selectors.
main.content .info {
  font-size: 0.8em;
}

⚠️ 注意!

因为 @extend 更新包含扩展选择器的样式规则,它们的样式在 级联 中具有优先权 扩展选择器的样式规则出现,not 基于@extend出现的位置。 这可能会造成混淆,但请记住:如果您将扩展类添加到您的 HTML,这与那些规则的优先级相同!

占位符选择器

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

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

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

私有占位符

module members 一样,占位符选择器可以通过其名称以“-”或“_”开头来标记为私有。 私有占位符选择器只能在定义它的样式表中扩展。 对于任何其他样式表,该选择器看起来好像不存在。

扩展范围

当一个样式表扩展一个选择器时,该扩展只会影响写在_upstream_模块中的样式规则——也就是说,由该样式表使用 @use 规则 加载的模块 或 @forward 规则those 模块加载的模块,等等。 这有助于使您的 @extend 规则更具可预测性,确保它们只影响您在编写它们时知道的样式。

⚠️ 注意!

如果您使用 @import 规则,则扩展根本没有范围。 它们不仅会影响您导入的每个样式表,还会影响每个导入您的样式表的样式表,以及这些样式表导入的所有其他样式表,等等。 没有 @use,扩展是 global

强制和可选扩展

通常,如果 @extend 与样式表中的任何选择器都不匹配,Sass 会产生错误。 这有助于防止拼写错误或重命名选择器而不重命名从它继承的选择器。 要求扩展选择器存在的扩展是_强制的_。

不过,这可能并不总是您想要的。 如果您希望 @extend 在扩展选择器不存在时不执行任何操作,只需将 !optional 添加到末尾即可。

扩展还是混合?

extends 和 mixins 都是在 Sass 中封装和重用样式的两种方式,这自然会产生什么时候使用哪一种的问题。 当您需要使用 arguments 配置样式时,mixins 显然是必需的,但如果它们只是一大块样式怎么办?

根据经验,当您表达语义类(或其他语义选择器)之间的关系时,extends 是最佳选择。 因为类为.error--serious的元素_是_错误,所以扩展.error是有意义的。 但对于非语义样式集合,编写混合可以避免级联问题,并使配置更容易。

💡 有趣的事实:

大多数网络服务器使用一种非常擅长处理重复的相同文本块的算法来压缩它们所服务的 CSS。 这意味着,虽然 mixin 可能会产生比 extends 更多的 CSS,但它们可能不会显着增加用户需要下载的数量。 因此,选择对您的用例最有意义的功能,而不是生成最少 CSS 的功能!

限制

不允许的选择器

兼容性(无复合扩展):Dart Sass ✓ | LibSass ✗ | Ruby Sass ✗

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

为了保持 @extend 的定义简单易懂,并保持实现简洁高效,该行为现已弃用,并将从未来版本中删除。

有关详细信息,请参阅重大更改页面。

只有_simple selectors_——像.infoa 这样的独立选择器可以被扩展。 如果可以扩展.message.info,则“@extend”的定义表示与扩展符匹配的元素的样式将被设置为就好像它们与.message.info匹配一样。 这与同时匹配 .message.info 是一样的,所以用它代替 @extend .message, .info 不会有任何好处。

类似地,如果 .main .info 可以扩展,它会做(几乎)与单独扩展 .info 相同的事情。 细微的差异不值得混淆,看起来好像在做一些本质上不同的事情,所以这也是不允许的。

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

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

HTML 启发式

@extend 交错复杂选择器 时,它不会生成祖先选择器的所有可能组合。 它可以生成的许多选择器不太可能真正匹配真正的 HTML,并且全部生成它们会使样式表太大而几乎没有实际价值。 相反,它使用 heuristic:它假定每个选择器的祖先都是独立的,不会与任何其他选择器的祖先交错。

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

aside .notice dd {
  // Sass doesn't generate CSS to match the <dd> in
  //
  // <header>
  //   <aside>
  //     <div class="warning">
  //       <div class="notice">
  //         <dd>...</dd>
  //       </div>
  //     </div>
  //   </aside>
  // </header>
  //
  // because matching all elements like that would require us to generate nine
  // new selectors instead of just two.
  @extend li;
}

Extend in @media

虽然在 @media 和其他 CSS at-rules 中允许使用 @extend,但不允许扩展出现在其 at-rule 之外的选择器。 这是因为扩展选择器仅适用于给定的媒体上下文,如果不复制整个样式规则,则无法确保在生成的选择器中保留限制。

scss
Scss
scss
@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;
}