# 摘要
当你在编写 css 代码的时候,是否遇到这样的困扰:不知道取什么 class 名?修改某个组件的样式,担心影响了其他组件?编写的组件样式如何复用?为了解决这些问题,聪明的程序猿发明了 BEM 命名法。
BEM 命名法,是对 css 命名的一种规范,将页面模块化,隔离样式,提高代码的复用性,减少后期的维护成本。BEM 的意思就是 Block (块)、Element (元素)、modifier (修饰符),通过双下划线
__或者双中划–链接。BEM 通常用于框架开发中,比如微信 WEUI、饿了么 element-ui、有赞 vant 等。笔者也是通过阅读这些优秀框架的源码,学习到了这一套 css 命名大法,从此走上人生巅峰,赢取白富美。
# 为什么需要 BEM 命名法
# 样式隔离,避免 css 样式污染
css 样式污染的根本原因,是因为 css 没有作用域。BEM 通过特殊的命名方式,给 css 创造一个 “作用域”,就能有效避免 css 样式全局污染。例如,给输入框命名
| # 普通 | |
| .base input {} | |
| # BEM 命名法 | |
| .base-input__inner {} | 
普通的命名法,会作用于所有 class='base’ 的后代元素。本来你只想给当前元素加样式,结果不小心影响了其他元素,这就是样式污染。
BEM 命名法,只会作用于 class='base-input__inner’ 的元素,达到样式隔离。 不会影响其他元素。
有人会说 css-module , 也能实现 css “作用域”,而且作用域更唯一,我为什么要用 BEM ?
# 代码更易覆盖
假如,你正在开发一个通用的输入框组件,用了 css-module 。打包后是这个样子
| .base-input-sdFh3sxLwo5uer {} | 
另一个人用了你的输入框组件,但是觉得样式不好看,想修改样式。试了半天,发现,根本无法用 css 精确选择你的组件,因为 .base-input 后面的 hash 值是动态的。于是,他捶胸顿足,发誓再也不用你的组件了。
# 代码更易读
还是刚刚的输入框组件, base-input__inner ,不需要我解释,你一眼就能看出: base 代表基础组件, input 代表输入框组件, inner 是组件中的某一块。
# 什么是 BEM 命名法
BEM 其实是块(block)、元素(element)、修饰符(modifier)的缩写,利用不同的区块,功能以及样式来给元素命名。这三个部分使用 __ 与 – 连接(这里用两个而不是一个是为了留下用于块儿的命名)。命名约定的模式如下:
| .block{} | |
| .block__element{} | |
| .block--modifier{} | 
- block代表更高级别的抽象或组件
- block__element代表- block的后代,用于形成一个完整的 block 的整体
- block–modifier代表- block的不同状态或不同版本
# 常用规范
- block element modifier 包含多个单词时,用一个中划线 - -链接,如:- el-dropdown-menu el-button
 
- block 和 element 用双下划线 - __链接,例:- 表单项 - form__item
- 导航项 - menu__item
 
- element 和 modifier 用双中划线 - –链接,如表示按钮的不同状态:- 默认: - el-button–default
- 成功: - el-button–success
 
- 用 js 控制样式时,css 命名用 - is-开头,如- is-success
- is-failed
- is-disabled
 
# 常用的元素名
- 表单元素 - form- form-item- input- select- radio- checkbox- switch- rate- datePicker
- 导航元素 - nav- subnav- menu- tab
- 提示 - alert- message- messageBox- notification
- 数据展示 - table- process- tree- pagiantion
- 其他 - button- icon
# 如何用好 BEM 命名法
# 页面命名
用 page-开头 ,page 表示这是一个页面,而不是组件。
给页面命名时,BEM 可以搭配 css-module 一起使用。既能保证打包后选择器的唯一,又容易调试。例如
| # 编译前 | |
| .page-index {} | |
| .page-zufang {} | |
| # 编译后 | |
| .page-index-70yGFBg1eKjbSIwN {} | |
| .page-zufang-mFTy62A1t83zjDbh {} | 
使用 css-module ,打包后的 css 名是可以修改的参考
| // 让打包后的文件更容易识别 | |
|  { | |
| test: /\.css$/, | |
| use: [ | |
|      { | |
| loader: 'css-loader', | |
| options: { | |
| modules: true, | |
| localIdentName: '[local]--[hash:base64:5]' | |
|       } | |
|     } | |
|   ] | |
| } | 
页面中的选择器,都嵌套在页面根选择器内 ( .page-xxx ),保证所有样式,只作用于当前页面。例如
| <!-- 页面命名 page-home --> | |
| <div class="page-home"> | |
|     <div class="the-form"> | |
|         <div class="the-form-item"> | |
| <div class="the-input"></div> | |
|         </div> | |
|     </div> | |
|     <div class="the-table"> | |
| <div class="the-table-content"></div> | |
|     </div> | |
| </div> | |
| <style lang='scss'> | |
| .page-home { | |
| .the-form {} | |
| .the-form-item {} | |
| .the-input {} | |
| .the-table {} | |
| .the-table-content {} | |
| } | |
| </style> | 
# 公共组件命名
用 base-开头 ,base 表示公共组件。
| <div class="base-input"> | |
|     <input class="base-input__inner"/>    | |
| </div> | |
| <!-- 选择器避免嵌套,降低选择器权重 --> | |
| <style> | |
| .base-input {} | |
| .base-input__inner {} | |
| </style> | 
公共组件的每一个 class 名,带上组件的作用域前缀,如 base-input__inner 的作用域前缀是 base-input 。
选择器不宜嵌套,让选择器的权重尽可能低。原因如下:
base-input__inner 已经具有有作用域了,无需再嵌套。
由于组件选择器权重较低,在组件外修改组件样式时,覆盖样式非常方便。
# 局部组件命名
用 the-开头 ,the 表示某一特定的组件。
| <div class="the-header"> | |
|     <div class="the-header__title" /> | |
|     <div class="the-header__desc"> | |
| </div> | |
| <!-- 选择器避免嵌套,降低选择器权重 --> | |
| <style> | |
| .the-header {} | |
| .the-header__title {} | |
| .the-header__desc {} | |
| </style> | 
局部组件的每一个 class 名,带上组件的作用域前缀,如 the-header__title 的作用域前缀是 the-header 。
局部组件,也不宜嵌套, 降低选择器权重。
局部组件也可以搭配 css-module 一起用,因为局部组件只给少数特定页面使用,修改样式,可以在组件内部直接修改。
# 其他注意事项
# 命名语义化
怎样衡量你的命名是语义化的?让一个人新人,来看一下你的代码,不需要解释,就能知道这个类的作用,就比较语义化。
通常,可以根据模块的功能而命名,如页面头部 header、导航栏 nav、主体 main、侧边栏 sidebar、底部 footer 等,这样整个页面看起来就比较清晰了,维护起来也比较方便。
| # bad | |
| .fl { ... } | |
| .fr { ... } | |
| # good  | |
| # 左浮动 | |
| .is-float-left { ... } | |
| # 右浮动 | |
| .is-float-right { ... } | 
上面的代码,fl、fr 之类的命名,表达意思不够清晰,要知道具体的含义,还得去看代码。
而 is-float-left ,就表达得非常清晰。
# 使用 class (类) 选择器,避免使用 id、标签、伪类选择器
标签、伪类 等选择器范围太广,不具有 “作用域” 的作用,会污染全局样式。例如,下面的代码中, .the-header a 选择器会选中 the-header 所有后代元素
| <div class="the-header"> | |
| <a></a> | |
| </div> | |
| <style> | |
| /* bad */ | |
| .the-header a { ... } | |
| </style> | 
# 覆盖第三方组件样式时,重新起一个 class 名
使用原来的 class 名修改样式,可能会不小心影响了后代组件的样式。为了消除这个隐患,可以重新起一个 class 名,只作用于当前组件。例如修改 ant-design 的输入框组件样式
| <div className="the-form"> | |
|     <Input className="the-input"> | |
| </div> | |
| <style> | |
| /* bad 会影响 the-form 后代的所有输入框 */ | |
| .the-form .ant-input {} | |
| /* good 只会只用于 className='the-input' 的输入框 */ | |
| .the-form .the-input{} | |
| </style> | 
# 总结
- 为什么需要 BEM 命名法
- 什么是 BEM 命名法
- 如何用好 BEM 命名法
- 其他注意事项
# 参考
- CSS BEM 书写规范
- css 命名规范 - BEM
