Sass 三種重用方式怎麼選:mixin、extend 與 function
@mixin、@extend、@function 常被放在一起講「都是為了重複使用」,但它們解決的其實不是同一件事。
| 產出什麼 | 一句話 | |
|---|---|---|
@mixin | 一段樣式,複製到呼叫處 | 「幫我把這段寫在這裡」 |
@extend | 合併後的選取器 | 「我跟它長得一樣」 |
@function | 一個值 | 「幫我算出這個數字」 |
同一個需求的三種寫法
需求:三個按鈕變體共用基底樣式。
混入:
scss
@mixin btn-base {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.btn-primary { @include btn-base; background: #2965f1; }
.btn-danger { @include btn-base; background: #e03131; }css
.btn-primary { padding: 8px 16px; border-radius: 4px; cursor: pointer; background: #2965f1; }
.btn-danger { padding: 8px 16px; border-radius: 4px; cursor: pointer; background: #e03131; }繼承:
scss
%btn-base {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.btn-primary { @extend %btn-base; background: #2965f1; }
.btn-danger { @extend %btn-base; background: #e03131; }css
.btn-primary, .btn-danger { padding: 8px 16px; border-radius: 4px; cursor: pointer; }
.btn-primary { background: #2965f1; }
.btn-danger { background: #e03131; }函式做不到這件事,它回傳值,不產生宣告。函式適合的是另一種需求:
scss
@function space($n) { @return $n * 8px; }
.btn-primary { padding: space(1) space(2); }產出的差異
| 混入 | 繼承 | |
|---|---|---|
| 宣告出現次數 | 每個呼叫處各一份 | 只有一份 |
| 選取器 | 各自獨立 | 合併成串列 |
| 規則位置 | 在呼叫的地方 | 在被繼承者定義的地方 |
| 壓縮前體積 | 較大 | 較小 |
| gzip 後體積 | 差距很小 | 較小 |
能力差異
| 能力 | @mixin | @extend | @function |
|---|---|---|---|
| 帶參數 | ✅ | ❌ | ✅ |
可用在 @media 內 | ✅ | ❌ | ✅ |
| 跨檔案穩定 | ✅ | ⚠️ 難追 | ✅ |
| 回傳值 | ❌ | ❌ | ✅ |
| 接收樣式區塊 | ✅(@content) | ❌ | ❌ |
| 產出順序可預測 | ✅ | ❌ | 不適用 |
@extend 那兩個 ❌ 是硬限制,也是它在現代 RWD 專案裡用不太上的原因。
決策表
| 你的需求 | 用什麼 |
|---|---|
| 算一個數字或色彩出來 | @function |
| 從 map 取值並在取不到時報錯 | @function |
| 一段固定的樣式要重複寫 | @mixin |
| 同一段樣式但值不同 | @mixin + 參數 |
| 包一層媒體查詢或主題外殼 | @mixin + @content |
| 幾十個選取器共用一大段樣式,且都在同一層級 | @extend %placeholder |
| 不確定 | @mixin |
「不確定就用混入」不是敷衍
混入沒有順序問題、能帶參數、能進媒體查詢、跨檔案行為一致。它換來的成本只是壓縮後很小的體積差;@extend 換來的成本是三個月後你追不到那條選取器為什麼長那樣。
函式與混入常常一起用
典型的分工是「函式算值、混入組樣式」:
scss
@use "sass:math";
@function rem($px, $base: 16px) {
@return math.div($px, $base) * 1rem;
}
@mixin text($size, $line: 1.5) {
font-size: rem($size);
line-height: $line;
}
.title { @include text(24px); }那工具類別呢
第四種選擇:不重用 Sass,改成產生一批 工具類別 讓 HTML 直接套。
scss
@each $n in (1, 2, 3, 4) {
.p-#{$n} { padding: $n * 8px; }
}html
<div class="p-2">…</div>差別在 樣式的決定權寫在哪裡,混入寫在 .scss,工具類別寫在 HTML。兩者可以並存,但一個專案該有明確的主要風格。
常見問題
不確定該用哪個的時候怎麼辦?
用混入。它沒有繼承的順序問題,可以帶參數,也能寫在媒體查詢裡,行為完全可預測。產出的重複在壓縮與 gzip 之後成本很低,遠比日後追不到樣式從哪來划算。
混入的重複輸出真的沒關係嗎?
多數情況沒關係。重複的宣告字串正是 gzip 最擅長壓縮的東西,實際傳輸量增加有限。只有在幾十個選取器共用一大段完全相同的樣式、且都在同一個媒體查詢層級時,改用繼承才划算。
函式和混入可以搭配使用嗎?
可以,而且很常見。典型的分工是函式負責算出值、混入負責把值組成宣告,例如混入內部呼叫換算函式來決定字級與行高。
工具類別和混入該選哪個?
看樣式寫在哪裡。希望把樣式決定留在 HTML 的類別上就產生工具類別,希望 HTML 只有語意類別就用混入。兩者可以並存,但同一個專案應該有明確的主要風格。