Sass 與 BEM:用 & 串接類別名的取捨
BEM(Block、Element、Modifier)把元件結構寫進 類別名稱,而不是寫進選取器的巢狀層數。搭配 & 之後,原始碼可以保持巢狀的組織感,產出卻是一層層獨立的類別。
三個部分
.card 區塊 Block:獨立的元件
.card__title 元素 Element:區塊的一部分,兩個底線
.card--featured 修飾子 Modifier:變體或狀態,兩個連字號<article class="card card--featured">
<h3 class="card__title">標題</h3>
<p class="card__text">內文</p>
</article>用 & 串接
.card {
padding: 16px;
border: 1px solid #eee;
&__title {
font-size: 20px;
font-weight: 700;
}
&__text {
color: #666;
}
&--featured {
border-color: #2965f1;
}
}.card { padding: 16px; border: 1px solid #eee; }
.card__title { font-size: 20px; font-weight: 700; }
.card__text { color: #666; }
.card--featured { border-color: #2965f1; }每一條選取器都只有一層。 優先權 全部是 0,1,0,誰要覆蓋誰只看順序,不會出現「得再加一個類別才蓋得過去」的窘境。
為什麼這比巢狀好
對照 巢狀 的寫法:
.card {
.title { } // → .card .title,優先權 0,2,0
}BEM + & | 巢狀後代 | |
|---|---|---|
| 選取器長度 | 一層 | 跟著巢狀增加 |
| 優先權 | 固定 0,1,0 | 逐層升高 |
| 依賴 DOM 結構 | 不依賴 | 少一層 <div> 就失效 |
| 類別名稱 | 較長 | 較短 |
BEM 用「比較長的類別名」換掉「比較高的優先權與結構依賴」。在會長大的專案裡,這筆交易划算得多。
唯一的代價:搜尋不到
&__title 在原始碼裡不是字串 .card__title。從瀏覽器看到一個類別名,回頭全域搜尋會找不到。
解法是讓檔名對應區塊名
components/
├── _card.scss ← .card 相關的全部在這
├── _button.scss ← .btn
└── _nav.scss ← .nav看到 card__title 就去開 _card.scss。這個約定成立之後,搜尋不到就不是問題了。架構見 7-1 檔案架構。
不接受這個代價的話,就把名稱寫完整:
.card { padding: 16px; }
.card__title { font-size: 20px; }搜尋得到,但 .card 相關的樣式就不再集中在一個區塊裡。兩種都可以,團隊統一就好。
修飾子與狀態
.btn {
padding: 8px 16px;
&--primary { background: #2965f1; }
&--large { padding: 12px 24px; }
// 狀態用 is- 前綴,通常由 JavaScript 加上
&.is-loading {
opacity: 0.6;
pointer-events: none;
}
}<button class="btn btn--primary is-loading">送出</button>修飾子不能單獨使用
class="btn--primary" 沒有帶 btn 的話,基底樣式全部拿不到。修飾子只寫「與基底的差異」。
元素只有一層
// ❌ BEM 沒有這種東西
.card__header__title { }需要寫到第三段時,代表那個部分應該 獨立成新的區塊:
.card {
&__header { }
}
.card-title { // 獨立區塊
&__icon { }
}名稱裡的層級反映的是元件關係,不是 DOM 有幾層。 <div class="card__title"> 不管被包在幾層裡面,類別名都一樣。
常見的誤用
// ❌ 把 BEM 又寫成巢狀,兩邊的缺點都拿到了
.card {
&__header {
&__title { } // → .card__header__title
}
}
// ❌ 元素裡再包元素的巢狀後代
.card__header {
.card__title { } // → .card__header .card__title,優先權 0,2,0
}BEM 的整個重點就是 讓選取器維持一層,寫成上面這樣等於白做。
與工具類別並用
<article class="card mt-4">常見的分工是:元件本體用 BEM、一次性的間距與對齊用 工具類別。
界線要畫清楚,否則兩套系統會互相覆蓋,最後沒人知道某個 margin 到底來自哪裡。
常見問題
用 & 串接之後全域搜尋不到類別名,怎麼辦?
這是這個寫法唯一的代價。實務上的解法是讓檔名與區塊名一致,找 card__title 就去開 _card.scss。這個約定成立的話,搜尋不到反而不是問題。
元素可以有第二層嗎?
不行,BEM 的元素只有一層。需要寫成三段式的話,代表那個元素應該獨立成新的區塊。名稱裡的層級反映的是元件關係,不是 DOM 的巢狀深度。
修飾子要不要跟區塊寫在一起?
HTML 上兩個類別要並存,區塊類別提供基底、修飾子只寫差異。單獨使用修飾子而不帶區塊類別是常見的誤用,那樣就失去共用基底樣式的意義。
BEM 和工具類別可以並用嗎?
可以,常見的分工是元件本體用 BEM、間距與對齊這類一次性調整用工具類別。但要有明確界線,否則兩套系統會互相覆蓋,最後誰也說不清樣式從哪來。