Sass @extend 與 %placeholder:合併選取器與它的代價
混入 是把樣式 複製 一份,@extend 則是把 選取器合併 到同一條規則上。宣告只寫一次,看起來更「乾淨」,但代價藏在產出的順序與選取器裡。
它做了什麼
.btn {
padding: 8px 16px;
border-radius: 4px;
}
.btn-primary {
@extend .btn;
background: #2965f1;
}.btn, .btn-primary {
padding: 8px 16px;
border-radius: 4px;
}
.btn-primary {
background: #2965f1;
}.btn-primary 被 加進 .btn 那條規則的選取器串列,共用同一份宣告。
換成混入的話,.btn-primary 會自己擁有一整份 padding 與 border-radius。
placeholder 選取器
以 % 開頭的選取器 本身不會輸出,只有被繼承時才出現:
%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;
}.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 會連 被繼承者出現過的每一個位置 一起合併:
.btn { color: #333; }
.card .btn { margin: 0; }
.modal .btn:hover { color: #000; }
.btn-primary { @extend .btn; }.btn, .btn-primary { color: #333; }
.card .btn, .card .btn-primary { margin: 0; }
.modal .btn:hover, .modal .btn-primary:hover { color: #000; }一個 @extend 產生了三組合併。專案裡繼承鏈一長,產出的選取器數量會以驚人的速度成長,這就是 選取器爆炸。
不能跨媒體查詢
@media (min-width: 768px) {
%wide { padding: 32px; }
}
.card {
@extend %wide; // ❌ Error
}@extend 是把選取器塞進被繼承者所在的規則,而 @media 區塊外的規則無法塞進區塊內。這個限制沒有解法,遇到就改用 混入。
這是實務上最常撞到的牆
RWD 專案裡的樣式幾乎都會經過 @media,所以 @extend 能用的場合比想像中少得多。
順序不由你決定
.a { @extend %base; } // 寫在前面
%base { color: red; }
.b { color: blue; }
.b { @extend %base; } // 寫在後面合併後的規則會出現在 %base 定義的位置,不是 @extend 的位置。這讓「後面的規則覆蓋前面的」這個 層疊 直覺失效,除錯時很難追。
該不該用
| 情況 | 建議 |
|---|---|
| 同檔案內、語意上真的是繼承(按鈕變體) | @extend %placeholder 可以 |
樣式要進 @media | 只能用混入 |
| 需要帶參數 | 只能用混入 |
| 跨檔案、跨模組 | 用混入,@extend 追不動 |
| 不確定 | 用混入 |
混入產出的重複沒有你想的可怕
@extend 主要省下的是重複的宣告字串,而這正是 gzip 最擅長壓的東西。用可預測性換那幾 KB,通常不划算。三者的完整比較見 三種重用方式怎麼選。
!optional
繼承一個不存在的選取器會編譯失敗。確定它可能不存在時加 !optional:
.btn-primary {
@extend %btn-base !optional;
}找不到就靜靜略過。這個旗標要謹慎使用,它會讓打錯字變成無聲的失敗。
常見問題
@extend 和 @include 差在哪?
@include 把樣式複製一份到呼叫處,產出有重複但各自獨立;@extend 則是把選取器合併到同一條規則上,宣告只出現一次,但規則的位置與順序由被繼承者決定。
placeholder 選取器是什麼?
以百分比符號開頭的選取器,它本身不會輸出任何 CSS,只有被 @extend 之後才會出現在合併的選取器裡。要用 @extend 的話應該一律繼承 placeholder,不要去繼承真實的類別。
為什麼 @extend 不能跨媒體查詢?
因為它是把選取器合併進被繼承者所在的那條規則,而媒體查詢區塊外的規則無法把選取器塞進區塊內。這時只能改用混入,把樣式複製一份進去。
什麼情況才該用 @extend?
同一個檔案裡、有明確語意繼承關係、且被繼承的樣式相當長的情況,例如一組按鈕變體共用的基底樣式。不確定的時候用混入,它的行為可預測得多。