@extend

在设计页面时,经常会出现一个类应该具有另一个类的所有样式以及它自己的特定样式的情况。例如, BEM 方法鼓励使用与块或元素类相同的元素的修饰符类。但这会造成 HTML 混乱,容易因忘记包含这两个类而出错,并且可能会将非语义样式问题带入您的标记中。

// html
<div class="error error--serious">
  Oh no! You've been hacked!
</div>

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

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

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

scss 语句 css 语句
.error {
  border: 1px #f00;
  background-color: #fdd;

  &--serious {
    @extend .error;
    border-width: 3px;
  }
}
.error, .error--serious {
  border: 1px #f00;
  background-color: #fdd;
}
.error--serious {
  border-width: 3px;
}

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

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

scss 语句 css 语句
.error:hover {
  background-color: #fee;
}

.error--serious {
  @extend .error;
  border-width: 3px;
}
.error:hover, .error--serious:hover {
  background-color: #fee;
}

.error--serious {
  border-width: 3px;
}
⚠️注意!
在您的样式表的其余部分被编译后,扩展被解析。特别是,它发生在父选择器被解析之后。这意味着如果你 @extend .error ,它不会影响 .error{&__icon{...}} 。这也意味着 SassScript 中的父选择器看不到扩展的结果。


运作机制

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

  • 它从不生成像 #main #footer ,这样可能无法匹配任何元素的选择器。
  • 它确保复杂的选择器是交错的,因此无论 HTML 元素的嵌套顺序如何,它们都能正常工作。
  • 它尽可能地修剪冗余选择器,同时仍然确保特异性大于或等于扩展器的特异性。
  • 它知道一个选择器何时匹配另一个选择器,并且可以将它们组合在一起。
  • 它智能地处理组合器、通用选择器和包含选择器的伪类。
scss 语句 css 语句
.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 `
` will be inside or // outside `
`, 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; }
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中,这些规则将具有相同的优先级!


占位符选择器

有时您想编写仅用于扩展的样式规则。在这种情况下,您可以使用占位符选择器,它以 % 开头代替 . 的类选择器。任何包含占位符的选择器,都不包含在 CSS 输出中,但包含扩展它们的选择器。

scss 语句 css 语句
.alert:hover, %strong-alert {
  font-weight: bold;
}

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


私有占位符

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


扩展范围

当一个样式表扩展选择器时,该扩展将只影响在上游模块中编写的样式规则,即该样式表使用 @use 规则或 @forward 规则加载的模块、这些模块加载的模块,等等。这有助于使您的 @extend 规则更具可预测性,确保它们只影响您编写规则时意识到的样式。

⚠️注意!
如果您使用 @import 规则,那么扩展根本不受范围限制。它们不仅会影响您导入的每个样式表,还会影响导入您的样式表的每个样式表,以及这些样式表导入的所有其他内容,等等。没有 @use ,扩展是全局的。


强制和可选扩展

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

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


扩展还是混合?

Extends Mixins 都是 Sass 中封装和重用样式的两种方式,这自然提出了何时使用哪一种的问题。当您需要使用参数配置样式时,显然需要使用 Mixin ,但是如果它们只是一大堆样式怎么办?

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


限制

只有简单的选择器——像 .infoor 这样的单独的选择器——可以被扩展。如果 .message.info 可以扩展,则 @extend 的定义,表示匹配扩展器的元素将被设置为就像它们匹配一样 .message.info 。这与同时匹配 .message .info 相同,因此编写它而不是 @extend .message,.info 不会有任何好处。

同样,如果 .main .info 可以扩展,它将做(几乎)与扩展自身 .info 相同的事情。细微的差异不值得混淆,因为它看起来像是在做一些截然不同的事情,所以这也是不允许的。

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

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


HTML 启发式

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

scss 语句 css 语句
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;
}
header .warning li, header .warning aside .notice dd, aside .notice header .warning dd {
  font-weight: bold;
}


扩展扩展@media

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

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

上篇: @function

下篇: @error