@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 一個選擇器應該繼承另一個選擇器的樣式。

程式碼遊樂場

SCSS 語法

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

  &--serious {
    @extend .error;
    border-width: 3px;
  }
}
程式碼遊樂場

Sass 語法

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

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


CSS 輸出

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


當一個類別繼承另一個類別時,Sass 會將所有符合延伸器條件的元素套用樣式,就好像它們也符合被延伸的類別一樣。當一個類別選擇器繼承另一個類別選擇器時,它的作用就像您在 HTML 中已經具有延伸類別的每個元素上都添加了被延伸的類別。您可以只寫 class="error--serious",Sass 會確保它的樣式就像它也有 class="error" 一樣。

當然,選擇器不僅僅在樣式規則中單獨使用。Sass 知道在選擇器使用的任何地方都要延伸。這確保您的元素的樣式完全符合被延伸的選擇器。

程式碼遊樂場

SCSS 語法

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

.error--serious {
  @extend .error;
  border-width: 3px;
}
程式碼遊樂場

Sass 語法

.error:hover
  background-color: #fee


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

CSS 輸出

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

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

⚠️ 注意!

延伸規則 (Extends) 會在您的樣式表編譯完成後才被解析。特別的是,它會在 父選擇器 解析之後發生。這表示如果您使用 @extend .error,它不會影響 .error { &__icon { ... } } 中的內部選擇器。這也表示 SassScript 中的父選擇器 無法看到延伸的結果。

運作方式運作方式 永久連結

Mixin(將樣式複製到目前的樣式規則中)不同,@extend 會更新包含被延伸選擇器的樣式規則,使它們也包含延伸選擇器。在延伸選擇器時,Sass 會執行 *智慧型合併*。

  • 它永遠不會產生像 #main#footer 這樣不可能匹配任何元素的選擇器。

  • 它確保複雜的選擇器會被交錯,以便它們在任何 HTML 元素巢狀順序下都能正常運作。

  • 它會盡可能地修剪冗餘的選擇器,同時仍確保特異性 (specificity) 大於或等於延伸者的特異性。

  • 它知道何時一個選擇器與另一個選擇器完全匹配,並可以將它們合併在一起。

  • 它可以智慧地處理 組合器通用選擇器包含選擇器的偽類別

程式碼遊樂場

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;
}
程式碼遊樂場

Sass 語法

.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 輸出

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 會更新包含被延伸選擇器的樣式規則,因此它們的樣式在 階層式樣式表 (Cascade) 中的優先順序取決於被延伸選擇器樣式規則出現的位置,*而不是* @extend 出現的位置。這可能會令人困惑,但請記住:這與您將被延伸的類別新增到 HTML 中時的優先順序相同!

佔位符選擇器佔位符選擇器 永久連結

有時您只想編寫一個 *僅* 用於被延伸的樣式規則。在這種情況下,您可以使用 佔位符選擇器,它們看起來像類別選擇器,但以 % 開頭而不是 .。任何包含佔位符的選擇器都不會包含在 CSS 輸出中,但延伸它們的選擇器會被包含。

程式碼遊樂場

SCSS 語法

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

%strong-alert:hover {
  color: red;
}
程式碼遊樂場

Sass 語法

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


%strong-alert:hover
  color: red

CSS 輸出

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




私有佔位符私有佔位符 永久連結

模組成員 一樣,佔位符選擇器可以透過在其名稱前加上 -_ 來標記為私有。私有佔位符選擇器只能在其定義它的樣式表內被延伸。對於任何其他樣式表,它看起來就像該選擇器不存在一樣。

延伸範圍延伸範圍 永久連結

當一個樣式表延伸一個選擇器時,該延伸只會影響在 *上游* 模組中編寫的樣式規則——也就是說,透過 @use 規則@forward 規則 由該樣式表載入的模組、由 *這些* 模組載入的模組,依此類推。這有助於使您的 @extend 規則更具可預測性,確保它們只影響您在編寫它們時知道的樣式。

⚠️ 注意!

如果您使用 @import 規則,擴展將完全沒有作用域。它們不僅會影響您導入的每個樣式表,還會影響導入您的樣式表的每個樣式表,以及這些樣式表導入的所有其他內容,依此類推。沒有 @use 的情況下,擴展是*全域的*。

強制與選用擴展強制與選用擴展 永久連結

通常,如果 @extend 與樣式表中的任何選擇器都不匹配,Sass 將會產生錯誤。這有助於防止輸入錯誤或重新命名選擇器時忘記重新命名繼承它的選擇器。需要被擴展的選擇器存在的擴展稱為*強制性*擴展。

然而,這可能並非總是您想要的。如果您希望在被擴展的選擇器不存在時 @extend 不執行任何操作,只需在結尾添加 !optional 即可。

擴展還是 Mixin?擴展還是 Mixin? 永久連結

擴展和 Mixin 都是 Sass 中封裝和重複使用樣式的方法,這自然而然地引發了一個問題:何時使用哪一個?當您需要使用 參數 配置樣式時,顯然需要使用 Mixin,但是如果它們只是一段樣式呢?

根據經驗,當您表達語義類別(或其他語義選擇器)之間的關係時,擴展是最佳選擇。因為具有 .error--serious 類別的元素*是*一個錯誤,所以它擴展 .error 是合理的。但是對於非語義的樣式集合,編寫 Mixin 可以避免層疊的麻煩,並使其更容易在後續進行配置。

💡 小知識

大多數網路伺服器會使用一種非常擅長處理重複相同文字區塊的演算法來壓縮它們提供的 CSS。這意味著,儘管 Mixin 可能會產生比擴展更多的 CSS,但它們可能不會顯著增加使用者需要下載的數量。因此,選擇最適合您的使用案例的功能,而不是產生最少 CSS 的功能!

限制限制 永久連結

不允許的選擇器不允許的選擇器 永久連結

相容性(不支援複合擴展)
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 相同。細微的差異不值得讓人誤以為它在做一些實質上不同的事情,所以這也是不允許的。

程式碼遊樂場

SCSS 語法

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

  @extend .main .info;
  //      ^^^^^^^^^^^
  // Error: write @extend .info instead.
}
程式碼遊樂場

Sass 語法

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

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

HTML 啟發式演算法HTML 啟發式演算法永久連結

@extend 交錯複雜的選擇器 時,它不會產生所有可能的祖先選擇器組合。它可以產生的許多選擇器不太可能真正匹配實際的 HTML,而且產生所有這些選擇器會使樣式表變得過大,而實際價值卻很小。相反地,它使用一種 啟發式演算法:它假設每個選擇器的祖先將是自包含的,而不會與任何其他選擇器的祖先交錯。

程式碼遊樂場

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;
}
程式碼遊樂場

Sass 語法

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 輸出

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


















@media 中的 Extend在 @media 中的 Extend 永久連結

雖然 @extend 允許在 @media 和其他 CSS at 規則 中使用,但不允許它擴展出現在其 at 規則之外的選擇器。這是因為擴展選擇器僅適用於給定的媒體環境,並且沒有辦法確保在生成的選擇器中保留該限制而不複製整個樣式規則。

程式碼遊樂場

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;
}
程式碼遊樂場

Sass 語法

@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