如何看待 CSS 中 BEM 的命名方式?
BEM的意思就是塊(block)、元素(element)、修飾符(modifier),是由Yandex團隊提出的一種CSS Class 命名方法。
類似於:
.block{}
.block__element{}
.block--modifier{}
A New Front-End Methodology: BEM
再爛的東西,如果真的毫無價值,是會馬上被歷史所淹沒的
每個領域都有沉澱下來對特定開發者在特定場景有用的東西
很贊同克軍那句取其精華去其糟粕,何為精華,自己根據使用場景取捨
可能大多數同學看到那麼多下劃線中劃線以及那麼長我靠還有駝峰的名字,就會覺得混亂和不爽
不爽是一個很主觀的詞兒,他除了解決心理問題,沒法解決生理問題
CSS這麼多年並沒有一個相對比較嚴謹的套路出來,寬鬆的寫法導致團隊成員寫法各異,丟在頁面都能跑起來,但混著做項目就不敢動或理不清別人寫的代碼
"這個CSS重寫一遍比修改老文件快",這樣的念頭幾乎所有人都曾有過.
我們團隊用BEM快1年,下面我來談談一些心得
了解什麼是 B.E.M
Block
!誤區:這個block並非inline-block里的block,
而是將所有東西都劃分為一個獨立的模塊,一個header是block,header里嵌套的搜索框是block,甚至一個icon一個logo也是block
block可以相互嵌套
Element
!誤區:如果一個Element-son是另一個Element-father的子元素,
那麼寫法是 Block__Element-father__Element-son_Modifer,嵌套多了會很長么?
不是的!!!
一個Block下的所有Element無論相互層級如何,都要攤開扁平的屬於Block
所以 BEM 最多只有 B+E+M 三級,不可能出現 B+E+E+..+E+M 超長class名,也要求E不能同名
Modifier
之前我們經常寫的 .current .active 等表達狀態
從Class中解讀B.E.M
我們常說CSS的注釋要寫WHY,而不是寫WHAT,看Class命名最好就知道是WHAT
BEM提出的一個概念是用連接符號來表達,它並不規定必須用什麼連接符,但規定用不同連接符做團隊內約定區分BEM 3類元素
例如我們組內約定
__雙下劃線代表B和E連接例如 menu__item
_單下劃線代表B和M或E和M的連接 例如 menu_active 或 menu__item_active
-中劃線同英語里做連字元例如 mod-menu 或 mod-menu__item 這裡 B或E或M需要多個單詞來描述,就使用中劃線
打字會抽搐吧...
你聽說過Emmet么?再不濟Zen Coding有聽說過吧?實在不行聽說過安利也行啊
FYI http://docs.emmet.io/filters/bem/
拆分Block到文件
我們並沒有用BEM推薦的拆分CSS到更多目錄里,圖片是拆目錄的.因為用的是 Grunt+LESS
團隊項目特色為N個相互獨立的移動端項目,Block並不會很多,所以文件扁平化很直觀,帶來的效率也相對高,如圖為其中某個項目的css部分
另外,寫block的時候需要新建less文件,字母排序,是否重名都很清晰
按ctrl+f查找class定位和按快捷鍵打開文件名沒啥大區別,更何況新版LESS還有source map
最後我們團隊正在開發對應模塊管理的工具,目標是向NPM一樣玩,同Alice一樣規劃解決方案
代碼復用
代碼風格可能文檔里說的也不是很詳細,不如直接對著他們的頁面按F12吧 http://beta.yandex.com/
BEM/OOCSS 風格對維護重用的class有極大幫助,適當的拆分block後組合,威力無窮
那個千年老栗子——如果我想將一個f30的類,改成f35怎麼辦?是掛羊頭賣狗肉的直接將.f30{font-size:30px}改成.f30{font-size:35}嗎?還是要進行全局搜索,改動所有的html的class名?
或者 Alice 裡面的
.text-size30{font-size:30px;}
.text-size20{font-size:20px;}
.text-size10{font-size:10px;}
用程度來劃分,而非具體數值,所以根本就不會存在.text-size30這麼個類,那個寫style上去有毛線區別.
在var.less里定義具體的數值
在 ui.less 里調用
BEM的任何一個block都可以到處用,這對模塊並不多的手機項目非常有利.
關於hax大神吐槽的不用ID和後代選擇器
ID
ID對於我和!important對於我一樣,並不否認價值,但想不起來上一次用是啥時候了.
說到這裡順便提一下 z-index的問題,有多少同學寫z-index的時候會寫1000+?有做過整站z-index規劃么? 同樣的用 class 如果能規劃好了,我是不傾向用id,也想不到有什麼非用ID不可的情況,性能什麼的,呵呵,測過,影響不大
特定場景例子:在騰訊,JS和CSS是分別2種團隊的人在寫,我們約定ID給JS,class給CSS和固定前綴的JS hook,不管是不是BEM,ID在我們這兩種團隊約定下也是不使用,並且也沒帶來啥問題
這個BEM寫block的時候是不用,但block相互嵌套的時候是用的,
例如一個狀態下需要變動多個表現,用後代選擇器一次性處理
性能以及JS/CSS代碼可維護性都有明顯優勢
節選自
節選自 http://yandex.st/search_islands_www/0.2.15/desktop.bundles/index/_index.css
Tag selector 是翻譯成標籤選擇器么
BEM是不允許用標籤選擇器的,一開始難以接受...
.menu li 能搞定的事情需要每個 li 都寫.menu-item
壞處
是 k 數增加么?gzip下真不是個問題,或者是寫代碼額外工作量?這難道不是動態生成的么?再不濟編輯器也可以隨便列編輯或複製當前行或代碼提示啊
好處
就是避免 li 里的 li 受影響
舉個例子,商品詳情頁是允許商家自定義標籤的,那麼商家展示區域標籤的祖先元素一旦用標籤選擇器定義了樣式,子子孫孫都要背負.
所以十分贊同在無法百分百確定不會嵌套同樣標籤的情況下不用標籤選擇器
團隊最重要的是統一
有一次討論連字元用中劃線還是下劃線的時候,誰也說服不了誰,
最後一個掌勺的拍板,大家統一用一個,而非同一個團隊多種風格.
這對上下游合作,內部合作都會極大的降低溝通成本
之所以用 BEM(部分),也是因為沒找到更好的類似規範,雖然有缺陷,但至少可以統一
討論一個東西,我們很容易找出他的槽點,但是提出更好解決方案的同學少之又少,
從BEM中我們可以學習他優秀的方面納為己用,
團隊合作永遠是統一高於一切
20160812 更新:
https://www.smashingmagazine.com/2016/06/battling-bem-extended-edition-common-problems-and-how-to-avoid-them/
BEM除了是一種新的命令方式之外,我個人認為他還是提出了模塊與模塊,組件與組件,模塊與組件之間的管理方式。如何通過一種命名方式更好的管理和維護他們。
僅除命名的方式出發,很多同學會接受不了,認為命名繁瑣,增加了文件體積,另外編寫樣式,寫選擇器麻煩,從而增加了工作量。如果僅從這兩方面來說他不好,或許有點過激。
從一種思想上來看,BEM還是有很多值得大家去思考的,特別是用於維護項目,或者跨團隊的開發當中,他的好處會明顯見長。
任何一樣東西能生存,都有其自己的優勢,當然萬物有得就必有失。這是相互的,至於我們前端人員,或者一個團隊如何取捨,還是需要從自已或團隊力量出發,有利用之,無利就不用了
用它的思想,不一定要用它的命名方式。改成這樣更簡潔些
.blockName-elName.modifier
.modifier 不單獨使用,所以也不會衝突
例1:
.menu
.menu-item
.menu-item.active
例2:
.shopCart
.shopCart-title
.shopCart-item
.shopCart-item.selected
SCSS讓CSS變的更像編程語言。於是,很自然的改變了CSS的傳統組織方式。
關於BEM爭議最大的就是它的命名風格,這樣:
&
- &
- …& &
- …& &
block + element + modifier,鼓勵一級一級的寫的非常具體,很長。
問題:
1. 這麼長,影響書寫效率吧。肯定會影響但這是個很大的問題嗎(自動提示會緩解一些)
2. html和css的size肯定會大一些。size大的顧慮是影響傳輸,在gzip面前可以忽略
3. 不爽。的確很違背習慣,但任何個人喜好和習慣作借囗都不職業
風格不重要。我更關心它的好處:
1. SCSS嵌套過多。超過3層就很難閱讀了。
2. 嵌套多,選擇器的層級就會多,性能不知不覺變差
3. 復用。這麼長的名,想衝突都難
還有一個代碼設計上的原則,不暴露抽象類。舉例:
以前:
&
- &
- xxx& &
.list是抽象的列表類。層疊的.list-member類,定義少量樣類就可以實現一個成員列表的樣式。
但是在其它編程語言里抽象類是不會被暴露出來的。借鑒BEM會是這樣:
&
- &
- xxx& &
不在html裡層疊抽象類,而是在SCSS里繼承:
%list { ... }
.member-list {
@extend %list;
}
.member-list__item {
// 不同的樣式規則
}
這樣更符合編程的特點。重要的是在維護上。假如變樣了需要繼承另一個抽象類,不需要改html,只要改css。這樣SoC更徹底。
風格無非是某種形式,可以約束人做到一致。背後的設計思想才值得應用。如果用BEM的風格,但沒做到抽象類的封裝,沒做到選擇器的扁平,那就是失敗的應用。
最後,我非常認同這種設計思想。但我還是不會照搬它的命名規則。太TM囧了!有過這樣得經歷:
產品改需求,從十幾個mod less文件中找到目標行,只花了10秒。
BEM適用有一定規模得項目和團隊,鄉村作坊隨意高興就好。
好不好自己去用一周,就有答案了,只有適合自己團隊得才叫好。
除了有時候名字太長,太長,太長,其他都還好。
瀉藥。
搬運微博吐槽(http://weibo.com/1960954893/Ac2LAgIjn)如下:
@顧軼靈 說:BEM 很火,看著就想吐
我回復說:的確,看著這樣的一坨屎流行,真是讓人無語。
吐槽完畢,下面正經分析一下。正如我很早就說過的(http://hax.iteye.com/blog/497338):我更感興趣的是不同的行為模式和背後的機制。
如果你稍微看一下就發現,它所謂的block(塊),其實就是UI的構成單元。如果是可復用的UI單元,那就是我們常說的widget或者component。只不過前者主要從產品經理和UI設計師的視角來說,後者則是前端工程師的視角。所以它後續所導出的那些,與以widget為中心的開發思路其實沒有什麼兩樣。唯一需要注意這體系的workflow是用xml或json寫ui然後通過xslt或bemhtml轉換到html,然後配合css。而常見的基於widget的開發是直接用JavaScript寫UI,然後配合widget的定製css。
所以你比較一下可以看出其中的相似性,差別無非是BEM更加聲明性一點。反正最終都是生成HTML。
這流程和「純正」的Web開發workflow其實是不一樣的,前者是UI中心的,HTML/CSS只是實現UI的技術。而後者不是UI中心的,而是信息中心的,即首先考慮信息本質即所謂「語義」,並以合適的標籤來標記,然後再考慮樣式和行為(即UI)。當然,秉持純正Web開發其實不容易,業界大多數公司的前端workflow也不完全是如此的。這也是為什麼產生那麼多奇怪的「最佳實踐」、「框架」的原因。因為這兩種方法論本來就是不適配的。
就我個人而言,除非項目是遊戲這樣幾乎是純體驗而不是信息流的東西——也就純粹只是用HTML5技術來實現它;除此之外,網站或者WebApp,我都選擇「純正」的Web開發方式。其原因很簡單,Web的基礎架構和技術(包括HTTP、REST、HTML、CSS等)是按照語義中心的方式設計出來的。如果採用UI中心的方法論,必然導致阻抗不匹配。其中包括,按照這種方法論寫出的網頁其實和通常認知的相反,性能反而更差——因為這種方法論傾向於導致更冗長的html和classname,更複雜的DOM結構,更多的CSS匹配計算。
特別注意一點,你會發現介紹BEM的文章里出現html代碼的頻率其實很少,因為大多數情況無非生成一堆帶有class的div/span。假如是這樣,那麼這種xml=&>html的轉換到底有什麼意義?
至於BEM語法、用下劃線而避免層級、只用class避免id和tagname等等,其實都是UI中心方法論所導出的結果,和給每個組件對象設置屬性的傳統UI開發方式幾乎完全一致,從語義中心方法論來看,自然是荒謬無比。當然我對此表示理解。具體的分析見這篇回答:http://www.zhihu.com/question/20658520/answer/15770608 。
暫時以上。如有評論再做針對補充。
如果考慮用 BEM 的話,不如進一步考慮用 CSS 模塊化,我在某寶的二級站點項目(算是一個中小型項目吧)中有過實踐,效果不錯。以 React 為例,目錄結構大致是:
&
&
&
index.jsx
index.less
SubComponent1.jsx
SubComponent1.less
SubComponent2.jsx
SubComponent2.less
& 說說幾點好處:
index.jsx
index.less
- 模塊化後名稱通常來說很短,我的習慣是Component的根元素均為「.container」,修飾器直接用「.active」、「.selected」即可,在編譯後可以根據相對路徑保證全局唯一
- 例如 index.less 中的 「.container」 會被編譯為「.your-project-id-components-mycomponent1-container-HASHVALUE」
- 再例如 SubComponent1.less 中的 「.selected」會被編譯為「.your-project-id-components-mycomponent1-subcomponent1-selected-HASHVALUE」
- HASHVALUE 為哈希值
- 當 Component 需要更名時,只需修改目錄名稱即可,編譯出來的完整 CSS 名稱會隨之改變
- 可以在 JavaScript 中獲得 less 中的變數值
- 編譯後甚至可以用完全沒有含義的 HASH 值作為 class name(類似 Github 和 Google 的效果)
進一步參考:
CSS-Loader https://github.com/webpack-contrib/css-loader
[譯]Webpack 2 快速入門(內附 CSS 模塊化部分) https://github.com/dwqs/blog/issues/46
附上自己的 Webpack 配置片段:
...
{
test: /.less$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true,
sourceMap: true,
getLocalIdent
}
},
"less-loader"
],
exclude: /node_modules/
},
...
function getLocalIdent(loaderContext, localIdentName, localName) {
if (loaderContext.resourcePath.startsWith(SRC_PATH)) {
const localNameInLowerCase = localName.toLowerCase();
const relativePath = path.relative(SRC_PATH, loaderContext.resourcePath);
const p = path.parse(relativePath);
let name = `${p.dir.toLowerCase().replace(///g, "-")}`;
if (p.name === "index") {
name += `--${localNameInLowerCase}`;
} else {
name += `-${p.name.toLowerCase()}--${localNameInLowerCase}`;
}
return name;
}
throw new Error("Out of src path.");
}
作為一個俄語人,對Яндекс.ру這個俄羅斯的百度感情不止一點半點。yandex這種bem的class類命名是值得借鑒的。
如果是我用的話,我會把他用在針對所有項目所開發的通用樣式命名中,或者至少是某個項目的公用樣式中去。很明顯,bem的命名法說明該樣式的復用性極高。
而項目的私有樣式,我還是傾向於 項目名-頁面名-功能名 這樣的方式來命名。
我的組裡在對待公有樣式和私有樣式上我確實讓大家區分這麼做了。BEM 試圖通過人工的方式來生成唯一的 ID (CSS 類名)——我覺得有點愚蠢。
其實就算一個前端不知道什麼是BEM,但頁面重構做多了不知不覺也會發現BEM這種命名方法比較容易好用。。。
但實際上頁面重構做得越久你會發現不管什麼命名,項目大了就還是會有重名或者破壞規則的命名情況。
所以我的經驗是不可能有什麼完美class命名規範的,遲早會寫出更多短橫線,或者放棄治療改用後代選擇器。。語義化的準則本身也不包括class命名,每個人命名習慣不同都合情合理,不用在這些細枝末節上糾結太久。
決戰BEM, 10個常見問題及其解決方案 - 知乎專欄
我一直不認為他是一種最優雅方式來命名css樣式。讓我拋棄它這麼長時間原因很多,其中有一點就是令人尷尬的語法。作為設計師的我不希望我性感的標題夾雜著骯髒的雙重下劃線和惡劣的雙連字元。但是作為開發者的我務實的看了一下,最終,構建用戶界面的模塊化方式和邏輯戰勝了我右腦的抱怨。我確實不建議讓它作為自己的客廳核心,但是當你需要一件救生衣(想像你在一個css的大海上),我想功能大於形式。
這個問題放到js也是一樣的,我一直要求js的命名一定要一眼看出是value variable還是function variable,是公有還是私有,得到的是copy出來的還是original的。
然而,絕大多數人會不採用。所以一改就出翔。沒注意什麼又出來了個「BEM」的概念,其實09年我在寫《編寫高質量代碼》這本書的時候,就已經提到過了這種命名方法,11年的時候被hax和winter反對了。最近看知乎的時候,發現這種命名方式又被拿出來討論,並且在多個問題里我的書被點名提到。沒想到兩年過去了,這個問題還在繼續。
反對這種命名方式的人,大多舉了個例子——如果我想將一個f30的類,改成f35怎麼辦?是掛羊頭賣狗肉的直接將.f30{font-size:30px}改成.f30{font-size:35}嗎?還是要進行全局搜索,改動所有的html的class名?
這其實舉了個非常不當的例子。因為在過去幾年裡,提出相同疑問的人不少,所以之前我寫過一篇博客來回應。《關於我書里提到的「掛多個類,使用類的組合」的一些補充》
另外,我個人並不認為less這樣的解決方案是對的。同樣寫過一篇博客《css長命名 VS LESS_阿當個人空間》
長命名有非常多的好處,「抽象」和「避免衝突」,避免「選擇符權重競賽」等等,上面克軍的回答很清晰,我就不贅述了。個人觀點,同樣的單詞,我會喜歡只寫一遍。
css path難道是用來吃的?
css預編譯語言配合嵌套的做法好不好我不知道,bem好不好我也不知道,我只是知道如果有人說出「性能問題」四個字,那他已經贏了、而且還是立於不敗之地那種……根本沒辦法討論。
然後,我不喜歡bem。太長了符號太多辣眼睛。
BEM寫久了容易變成強迫症,今天覺得這麼寫好,明天覺得那樣寫好,後天再改回去……
不過對CSS這種一旦嵌套起來能亂得你找不到北的語言來說,好處還是大於墨跡的
bem核心思想是組件化。一個組件必定是要能從上下文獨立出來的。可能我們平時css太隨意了,雖然命名不是按照bem的風格,也一定程度實戰了組件化,但是每個人的寫法是不一樣的,很難統一,別人修改你的代碼的時候由於不知道你的寫法會導致隨意開發濫行。bem約定了一種規範,大家在開發過程中遵守。事物都有兩面性,組件化必定帶來代碼體積的擴張,架構也更加複雜,byte大了,但是這在現代css開發中已經不是主要矛盾了,使用scss工具並遵守bem命名規則的組件化開發使項目可維護性更好。yandex的產品線基本都是這種風格的,大家可以看看,本人也是剛接觸它,打算把它總在項目里重構
並不是非要使用BEM,畢竟BEM的書寫也有相應的瑕疵,但是BEM的命名規範卻具有很大參考性
設計思想總是可以參考的。
應用場景也是可以靈活變化的。
在拒絕之前先去了解一下,總是不吃虧的。
在接受之後結合現狀做些調整,也是可以很棒的。
推薦閱讀: