优质的组件库都离不开BEM!

简介: 当你在编写css代码的时候,是否遇到这样的困扰: 不知道取什么class名? 修改某个组件的样式,担心影响了其他组件? 编写的组件样式如何复用?为了解决这些问题,聪明的程序猿发明了BEM命名法。

1. 摘要

当你在编写css代码的时候,是否遇到这样的困扰: 不知道取什么class名? 修改某个组件的样式,担心影响了其他组件? 编写的组件样式如何复用?为了解决这些问题,聪明的程序猿发明了BEM命名法。


BEM命名法,是对css命名的一种规范,将页面模块化,隔离样式,提高代码的复用性,减少后期的维护成本。BEM的意思就是Block(块)、Element(元素)、modifier(修饰符),通过双下划线 __ 或者双中划 -- 链接。


BEM通常用于框架开发中,比如微信WEUI、饿了么element-ui、有赞vant等。笔者也是通过阅读这些优秀框架的源码,学习到了这一套css命名大法,从此走上人生巅峰,赢取白富美。


2. 为什么需要BEM命名法

2.1 样式隔离,避免css样式污染

css样式污染的根本原因,是因为css没有作用域。BEM通过特殊的命名方式,给css创造一个“作用域”,就能有效避免css样式全局污染。例如,给输入框命名:

// 普通
.base input {}
// BEM命名法
.base-input__inner {}

普通的命名法, 会作用于所有 class='base' 的后代元素。本来你只想给当前元素加样式,结果不小心影响了其他元素,这就是样式污染。

BEM命名法,只会作用于 class='base-input__inner' 的元素, 达到样式隔离。 不会影响其他元素。 有人会说 css-module, 也能实现css“作用域”,而且作用域更唯一,我为什么要用BEM ?


2.2 代码更易覆盖

假如,你正在开发一个通用的输入框组件,用了 css-module。打包后是这个样子:

.base-input-sdFh3sxLwo5uer {}

另一个人用了你的输入框组件,但是觉得样式不好看,想修改样式。试了半天,发现,根本无法用css精确选择你的组件,因为 .base-input 后面的hash值是动态的。于是,他捶胸顿足,发誓再也不用你的组件了。


2.3 代码更易读

还是刚刚的输入框组件,base-input__inner,不需要我解释,你一眼就能看出:base 代表基础组件,input 代表输入框组件, inner 是组件中的某一块。


3. 什么是BEM命名法

BEM其实是块(block)、元素(element)、修饰符(modifier)的缩写,利用不同的区块,功能以及样式来给元素命名。这三个部分使用 __-- 连接(这里用两个而不是一个是为了留下用于块儿的命名)。命名约定的模式如下:

.block{}
.block__element{}
.block--modifier{}
  • block 代表更高级别的抽象或组件
  • block__element 代表 block 的后代,用于形成一个完整的 block 的整体
  • block--modifier 代表 block 的不同状态或不同版本


3.1 常用规范

  1. blockelementmodifier 包含多个单词时,用一个中划线-链接,如:
.el-dropdown-menu
  .el-button
  1. blockelement 用双下划线 __ 链接,例:
//表单项
  .form__item
  //导航项
  .menu__item
  1. elementmodifier 用双中划线 -- 链接,如表示按钮的不同状态:
//默认
  .el-button--default
  //成功
  .el-button--success
  1. 用js控制样式时,css命名用 is- 开头,如 is-successis-failedis-disabled


3.2 常用的元素名

  • 表单元素:formform-iteminputselectradiocheckboxswitchratedatePicker
  • 导航元素:navsubnavmenutab
  • 提示:alertmessagemessageBoxnotification
  • 数据展示:tableprocesstreepagiantion
  • 其他:buttonicon


4. 如何用好BEM命名法


4.1 页面命名

page- 开头,page 表示这是一个页面, 而不是组件。 给页面命名时,BEM可以搭配 css-module一 起使用。既能保证打包后选择器的唯一,又容易调试。例如

// 编译前
.page-index {}
.page-zufang {}
// 编译后
.page-index-70yGFBg1eKjbSIwN {}
.page-zufang-mFTy62A1t83zjDbh {}

使用css-module,打包后的clss名是可以修改的参考

# 让打包后的文件更容易识别
 {
   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>
    .page-home {
      .the-form {}
      .the-form-item {}
      .the-input {}
      .the-table {}
      .the-table-content {}
    }
  </style>


4.2 公共组件命名

base- 开头,base表示公共组件:

<div class="base-input">
  <input class="base-input__inner"/>   
</div>
// 选择器避免嵌套,降低选择器权重
.base-input {}
.base-input__inner {}

公共组件的每一个class名,带上组件的作用域前缀,如 base-input__inner 的作用域前缀是 base-input

选择器不宜嵌套,让选择器的权重尽可能低。原因如下:

  • base-input__inner 已经具有有作用域了,无需再嵌套。
  • 由于组件选择器权重较低,在组件外修改组件样式时,覆盖样式非常方便。


4.3 局部组件命名

the- 开头,the 表示某一特定的组件。

<div class="the-header">
  <div class="the-header__title" />
  <div class="the-header__desc">
</div>
// 选择器避免嵌套,降低选择器权重
.the-header {}
.the-header__title {}
.the-header__desc {}

局部组件的每一个class名,带上组件的作用域前缀,如 the-header__title 的作用域前缀是 the-header

局部组件,也不宜嵌套, 降低选择器权重。

局部组件也可以搭配 css-module 一起用,因为局部组件只给少数特定页面使用,修改样式,可以在组件内部直接修改。


5. 其他注意事项


5.1 命名语义化

怎样衡量你的命名是语义化的?让一个人新人,来看一下你的代码,不需要解释,就能知道这个类的作用,就比较语义化。

通常,可以根据模块的功能而命名,如页面头部header、导航栏nav、主体main、侧边栏sidebar、底部footer等,这样整个页面看起来就比较清晰了,维护起来也比较方便。

// bad
.fl { ... }
.fr { ... }
// good 
// 左浮动
.is-float-left { ... }
// 右浮动
.is-float-right { ... }

上面的代码,flfr 之类的命名,表达意思不够清晰,要知道具体的含义,还得去看代码,而 is-float-left,就表达得非常清晰。


5.2 使用 class(类) 选择器,避免使用 id、标签、伪类选择器

标签、伪类 等选择器范围太广,不具有“作用域”的作用,会污染全局样式。例如,下面的代码中,.the-header a 选择器会选中 the-header 所有后代元素 <a></a>

<div class="the-header">
  <a></a>
</div>
// bad
.the-header a { ... }


5.3 覆盖第三方组件样式时,重新起一个class名

使用原来的class名修改样式,可能会不小心影响了后代组件的样式。为了消除这个隐患,可以重新起一个class名,只作用于当前组件。例如修改ant-design的输入框组件样式

<div className="the-form">
  <Input className="the-input" />
</div>
// bad 会影响the-form 后代的所有输入框
.the-form .ant-input {}
// good 只会只用于className='the-input'的输入框
.the-form .the-input{}


目录
相关文章
|
2月前
|
开发框架 JavaScript API
uniapp知识大杂烩?
uniapp知识大杂烩?
|
2月前
|
JavaScript 前端开发 程序员
程序员必备技能之JS模块化,改变你的JavaScript开发方式!(一)
程序员必备技能之JS模块化,改变你的JavaScript开发方式!
|
前端开发 JavaScript 小程序
7 款最棒的开源 React UI 库测评 - 特别针对国内使用场景推荐
优秀的 React UI 组件库,帮我们节省开发时间,提高开发效率,统一设计语言。更棒的是内置的功能复杂,我们自己很难处理的常用组件,比如表格、表单、富文本编辑器、时间日期选择器、实时拖拽组件等,再进一步,还有帮我们把组件的轮子装好的 React admin 后台管理系统。本文推荐 7 款适用于中文使用者习惯的开源 React UI 库,特别针对国内使用场景推荐。
|
移动开发 前端开发 小程序
7 款最棒的 React 移动端 UI 组件库 - 特别针对国内使用场景推荐
优秀的 React UI 移动端组件库和模版框架,帮我们节省开发时间,提高开发效率,统一设计语言。更棒的是内置的功能复杂,我们自己很难处理的常用组件,比如表格、表单、富文本编辑器、时间日期选择器、实时拖拽组件等,再进一步,还有帮我们把组件的轮子装好的 React admin 后台管理系统。本文推荐 7 款适用于中文使用者习惯的开源 React 移动端 UI 库,特别针对国内使用场景推荐。
4799 0
|
14天前
|
开发者
代码复用的艺术:组件与库的力量
【6月更文挑战第20天】代码复用提升效率,组件和库是关键。组件,独立部署的软件单元,封装功能,降低系统复杂度。库,预编代码集合,提供通用功能。复用带来高效开发,减少错误,促进团队协作,降低维护成本。选择合适组件库,遵循设计原则,合理使用,编写可复用代码,实现软件开发的最佳实践。
|
2月前
|
小程序 前端开发 API
一文就知道uniapp等跨端开发的使用场景,学习成本,如何快速使用,基本语法等
uniapp是一个跨平台开发各种各样应用的一套框架。只需要写一套代码,可以适配多达14种产品类型,比如H5移动端、微信小程序及各种其他小程序,ios、安卓等接近原生APP的应用(可以上架到App Store或应用商店)。所以这里的多端,指的并不是PC、平板、手机端,而是移动端优先,开发者可以一次编码,分别编译为小程序和 Android 以及 iOS 应用,实现多端开发
153 0
|
2月前
|
JavaScript 前端开发 IDE
TypeScript在大型前端项目中的价值与实践策略
【4月更文挑战第7天】本文探讨了TypeScript在大型前端项目中的价值和实践策略。 TypeScript通过静态类型检查、代码提示、接口与泛型提高代码质量和开发效率。在大型项目中,可采用逐步迁移策略,制定类型规范,利用IDE特性,并维护类型定义文件。通过CI/CD和培训分享,团队能充分发挥TypeScript优势,提升项目可维护性、可扩展性和开发效率。
33 0
|
2月前
|
Web App开发 JavaScript 前端开发
程序员必备技能之JS模块化,改变你的JavaScript开发方式!(二)
程序员必备技能之JS模块化,改变你的JavaScript开发方式!
|
2月前
|
开发工具 git
uniapp项目实践拓展章:代码统一风格
uniapp项目实践拓展章:代码统一风格
65 0
|
8月前
|
开发框架 前端开发 JavaScript
BootstrapBlazor企业级组件库:前端开发的革新之路
BootstrapBlazor企业级组件库:前端开发的革新之路
86 0