@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 <selector>,它告诉 Sass 一个选择器应该继承另一个选择器的样式。

¥Sass’s @extend rule solves this. It’s written @extend <selector>, and it tells Sass that one selector should inherit the styles of another.

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" 一样。

¥When one class extends another, Sass styles all elements that match the extender as though they also match the class being extended. When one class selector extends another, it works exactly as though you added the extended class to every element in your HTML that already had the extending class. You can just write class="error--serious", and Sass will make sure it’s styled as though it had class="error" as well.

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

¥Of course, selectors aren’t just used on their own in style rules. Sass knows to extend everywhere the selector is used. This ensures that your elements are styled exactly as if they matched the extended selector.

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 中的父选择器 看不到 extend 的结果。

¥Extends are resolved after the rest of your stylesheet is compiled. In particular, it happens after parent selectors are resolved. This means that if you @extend .error, it won’t affect the inner selector in .error { &__icon { ... } }. It also means that parent selectors in SassScript can’t see the results of extend.

工作原理工作原理 permalink

¥How It Works

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

¥Unlike mixins, which copy styles into the current style rule, @extend updates style rules that contain the extended selector so that they contain the extending selector as well. When extending selectors, Sass does intelligent unification:

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

    ¥It never generates selectors like #main#footer that can’t possibly match any elements.

  • 它确保复杂的选择器是交错的,以便无论 HTML 元素以何种顺序嵌套,它们都可以工作。

    ¥It ensures that complex selectors are interleaved so that they work no matter which order the HTML elements are nested.

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

    ¥It trims redundant selectors as much as possible, while still ensuring that the specificity is greater than or equal to that of the extender.

  • 它知道一个选择器何时与另一个选择器匹配,并且可以将它们组合在一起。

    ¥It knows when one selector matches everything another does, and can combine them together.

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

    ¥It intelligently handles combinators, universal selectors, and pseudo-classes that contain selectors.

Playground

SCSS Syntax

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

Sass Syntax

.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

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 类似,但在单个选择器上。

¥You can directly access Sass’s intelligent unification using selector functions! The selector.unify() function returns a selector that matches the intersection of two selectors, while the selector.extend() function works just like @extend, but on a single selector.

⚠️ Heads up!

由于 @extend 更新包含扩展选择器的样式规则,因此它们的样式在 级联 中的优先级取决于扩展选择器的样式规则出现的位置,而不是基于 @extend 出现的位置。这可能会令人困惑,但请记住:如果你将扩展类添加到 HTML,则这些规则的优先级相同!

¥Because @extend updates style rules that contain the extended selector, their styles have precedence in the cascade based on where the extended selector’s style rules appear, not based on where the @extend appears. This can be confusing, but just remember: this is the same precedence those rules would have if you added the extended class to your HTML!

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

¥Placeholder Selectors

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

¥Sometimes you want to write a style rule that’s only intended to be extended. In that case, you can use placeholder selectors, which look like class selectors that start with % instead of .. Any selectors that include placeholders aren’t included in the CSS output, but selectors that extend them are.

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

¥Private Placeholders

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

¥Like module members, a placeholder selector can be marked private by starting its name with either - or _. A private placeholder selector can only be extended within the stylesheet that defines it. To any other stylesheets, it will look as though that selector doesn’t exist.

扩展范围扩展范围 permalink

¥Extension Scope

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

¥When one stylesheet extends a selector, that extension will only affect style rules written in upstream modules—that is, modules that are loaded by that stylesheet using the @use rule or the @forward rule, modules loaded by those modules, and so on. This helps make your @extend rules more predictable, ensuring that they affect only the styles you were aware of when you wrote them.

⚠️ Heads up!

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

¥Extensions aren’t scoped at all if you’re using the @import rule. Not only will they affect every stylesheet you import, they’ll affect every stylesheet that imports your stylesheet, everything else those stylesheets import, and so on. Without @use, extensions are global.

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

¥Mandatory and Optional Extends

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

¥Normally, if an @extend doesn’t match any selectors in the stylesheet, Sass will produce an error. This helps protect from typos or from renaming a selector without renaming the selectors that inherit from it. Extends that require that the extended selector exists are mandatory.

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

¥This may not always be what you want, though. If you want the @extend to do nothing if the extended selector doesn’t exist, just add !optional to the end.

扩展还是混合?扩展还是混合? permalink

¥Extends or Mixins?

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

¥Extends and mixins are both ways of encapsulating and re-using styles in Sass, which naturally raises the question of when to use which one. Mixins are obviously necessary when you need to configure the styles using arguments, but what if they’re just a chunk of styles?

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

¥As a rule of thumb, extends are the best option when you’re expressing a relationship between semantic classes (or other semantic selectors). Because an element with class .error--serious is an error, it makes sense for it to extend .error. But for non-semantic collections of styles, writing a mixin can avoid cascade headaches and make it easier to configure down the line.

💡 Fun fact:

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

¥Most web servers compress the CSS they serve using an algorithm that’s very good at handling repeated chunks of identical text. This means that, although mixins may produce more CSS than extends, they probably won’t substantially increase the amount your users need to download. So choose the feature that makes the most sense for your use-case, not the one that generates the least CSS!

局限性局限性 permalink

¥Limitations

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

¥Disallowed Selectors

兼容性 (No Compound Extensions):
Dart Sass
LibSass
Ruby Sass

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

¥LibSass and Ruby Sass currently allow compound selectors like .message.info to be extended. However, this behavior doesn’t match the definition of @extend: instead of styling elements that match the extending selector as though it had class="message info", which would be affected by style rules that included either .message or .info, it only styled them with rules that included both .message and info.

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

¥In order to keep the definition of @extend straightforward and understandable, and to keep the implementation clean and efficient, that behavior is now deprecated and will be removed from future versions.

详细信息请参见 重大变更页面

¥See the breaking change page for more details.

只能扩展简单的选择器(例如 .infoa 等单独的选择器)。如果 .message.info 可以扩展,那么 @extend 的定义表明与扩展器匹配的元素将被设计为与 .message.info 匹配一样。这与同时匹配 .message.info 是一样的,所以用 @extend .message, .info 来代替 .message 并没有任何好处。

¥Only simple selectors—individual selectors like .info or a—can be extended. If .message.info could be extended, the definition of @extend says that elements matching the extender would be styled as though they matched .message.info. That’s just the same as matching both .message and .info, so there wouldn’t be any benefit in writing that instead of @extend .message, .info.

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

¥Similarly, if .main .info could be extended, it would do (almost) the same thing as extending .info on its own. The subtle differences aren’t worth the confusion of looking like it’s doing something substantially different, so this isn’t allowed either.

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

¥HTML Heuristics

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

¥When @extend interleaves complex selectors, it doesn’t generate all possible combinations of ancestor selectors. Many of the selectors it could generate are unlikely to actually match real HTML, and generating them all would make stylesheets way too big for very little real value. Instead, it uses a heuristic: it assumes that each selector’s ancestors will be self-contained, without being interleaved with any other selector’s ancestors.

Playground

SCSS Syntax

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

Sass Syntax

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

CSS Output

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


















@media 中扩展在 @media 中扩展 permalink

¥Extend in @media

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

¥While @extend is allowed within @media and other CSS at-rules, it’s not allowed to extend selectors that appear outside its at-rule. This is because the extending selector only applies within the given media context, and there’s no way to make sure that restriction is preserved in the generated selector without duplicating the entire style 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