Skip to content

Sass @extend 與 %placeholder:合併選取器與它的代價

Sass @extend 與 placeholder 選取器

混入 是把樣式 複製 一份,@extend 則是把 選取器合併 到同一條規則上。宣告只寫一次,看起來更「乾淨」,但代價藏在產出的順序與選取器裡。

它做了什麼

scss
.btn {
  padding: 8px 16px;
  border-radius: 4px;
}

.btn-primary {
  @extend .btn;
  background: #2965f1;
}
css
.btn, .btn-primary {
  padding: 8px 16px;
  border-radius: 4px;
}
.btn-primary {
  background: #2965f1;
}

.btn-primary加進 .btn 那條規則的選取器串列,共用同一份宣告。

換成混入的話,.btn-primary 會自己擁有一整份 paddingborder-radius

placeholder 選取器

% 開頭的選取器 本身不會輸出,只有被繼承時才出現:

scss
%btn-base {
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
}

.btn-primary {
  @extend %btn-base;
  background: #2965f1;
}

.btn-ghost {
  @extend %btn-base;
  border: 1px solid currentColor;
}
css
.btn-primary, .btn-ghost {
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
}
.btn-primary { background: #2965f1; }
.btn-ghost { border: 1px solid currentColor; }

%btn-base 完全沒出現在產出裡。

要用 @extend 就一定要搭 placeholder

繼承真實類別(@extend .btn)會把該類別 所有出現過的地方 都拖進來,包括 .card .btn.btn:hover 這些,產出很快就失控。繼承 %placeholder 則只有你定義的那一段。

選取器爆炸

@extend 會連 被繼承者出現過的每一個位置 一起合併:

scss
.btn { color: #333; }
.card .btn { margin: 0; }
.modal .btn:hover { color: #000; }

.btn-primary { @extend .btn; }
css
.btn, .btn-primary { color: #333; }
.card .btn, .card .btn-primary { margin: 0; }
.modal .btn:hover, .modal .btn-primary:hover { color: #000; }

一個 @extend 產生了三組合併。專案裡繼承鏈一長,產出的選取器數量會以驚人的速度成長,這就是 選取器爆炸

不能跨媒體查詢

scss
@media (min-width: 768px) {
  %wide { padding: 32px; }
}

.card {
  @extend %wide;   // ❌ Error
}

@extend 是把選取器塞進被繼承者所在的規則,而 @media 區塊外的規則無法塞進區塊內。這個限制沒有解法,遇到就改用 混入

這是實務上最常撞到的牆

RWD 專案裡的樣式幾乎都會經過 @media,所以 @extend 能用的場合比想像中少得多。

順序不由你決定

scss
.a { @extend %base; }   // 寫在前面
%base { color: red; }
.b { color: blue; }
.b { @extend %base; }   // 寫在後面

合併後的規則會出現在 %base 定義的位置,不是 @extend 的位置。這讓「後面的規則覆蓋前面的」這個 層疊 直覺失效,除錯時很難追。

該不該用

情況建議
同檔案內、語意上真的是繼承(按鈕變體)@extend %placeholder 可以
樣式要進 @media只能用混入
需要帶參數只能用混入
跨檔案、跨模組用混入,@extend 追不動
不確定用混入

混入產出的重複沒有你想的可怕

@extend 主要省下的是重複的宣告字串,而這正是 gzip 最擅長壓的東西。用可預測性換那幾 KB,通常不划算。三者的完整比較見 三種重用方式怎麼選

!optional

繼承一個不存在的選取器會編譯失敗。確定它可能不存在時加 !optional

scss
.btn-primary {
  @extend %btn-base !optional;
}

找不到就靜靜略過。這個旗標要謹慎使用,它會讓打錯字變成無聲的失敗。

常見問題

@extend 和 @include 差在哪?

@include 把樣式複製一份到呼叫處,產出有重複但各自獨立;@extend 則是把選取器合併到同一條規則上,宣告只出現一次,但規則的位置與順序由被繼承者決定。

placeholder 選取器是什麼?

以百分比符號開頭的選取器,它本身不會輸出任何 CSS,只有被 @extend 之後才會出現在合併的選取器裡。要用 @extend 的話應該一律繼承 placeholder,不要去繼承真實的類別。

為什麼 @extend 不能跨媒體查詢?

因為它是把選取器合併進被繼承者所在的那條規則,而媒體查詢區塊外的規則無法把選取器塞進區塊內。這時只能改用混入,把樣式複製一份進去。

什麼情況才該用 @extend?

同一個檔案裡、有明確語意繼承關係、且被繼承的樣式相當長的情況,例如一組按鈕變體共用的基底樣式。不確定的時候用混入,它的行為可預測得多。

延伸閱讀