本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
背景
最近老大在维护别人的代码时,发现我们团队写的样式各有种的想法及风格,这在后续维护会增加一定的难度,所以老大决定统一样式的会名规范,所以就安排我去调研及实践,下面是我调研的结果。
什么是 BEM 命名规范
BEM 由 Yandex 团队提出的一种前端 CSS 命名方法论。BEM 是 BlockElementModifier
的缩写 ,其中B 表示块(block)、E 表示元素(element)、M 表示修饰符(modifier)。
这三个部分通常使用__
与--
连接。即: .块__元素--修饰符{}
Block
:代表了一个独立的块级元素,可以理解为功能组件块。一个 Block 就是一个独立的区块,比如头部是个 block,表单功能输入框是一个block,block可大可小。Element
:是 Block 的一部分不能独立来使用的,所有的 Element 都是与 Block 紧密关联的。例如一个带有icon
的输入框,那么 这个icon
就是这个输入框 Block 的一个 Element,脱离了输入框的 Block 那么这个 icon 就没有意义。Modifier
:是用来修饰 Block 或 Element,表示 block 或者 element 在外观或行为上的改变。例如,上面提到的输入框 Block,当鼠标悬停时边框高亮,那么这个高亮的效果就应该用 Modifier 来实现。
上图绿色表 block
, 蓝色表示 element
, 红色表示 modifier
。
为什么要用 BEM?
性能
CSS 引擎查找样式表,对每条规则都按从右到左的顺序去匹配,以下这段代码看起来很快,实际上很慢。通常我们会认为浏览器是这样工作的:找到唯一ID元素ul-id
—> 把样式应用到li
元素上。
事实上: 从右到左进行匹配,遍历页面上每个 li
元素并确定其父元素
#ul-id li {}
所以不要让你的css超过三层。
语义化
BEM 的关键是光凭名字就可以告诉其他开发者某个标记是用来干什么的。 通过浏览HTML代码中的class属性,你就能够明白模块之间是如何关联的:有一些仅仅是组件,有一些则是这些组件的子孙或者是元素,还有一些是组件的其他形态或者是修饰符。
常规的命名法示例:
代码语言:javascript复制 <div class="article">
<div class="body">
<button class="button-primary"></button>
<button class="button-success"></button>
</div>
</div>
这种写法从 DOM 结构和类命名上可以了解每个元素的意义,但无法明确其真实的层级关系。在 css 定义时,也必须依靠层级选择器来限定约束作用域,以避免跨组件的样式污染。
使用了 BEM 命名方法的示例:
代码语言:javascript复制<div class="article">
<div class="article__body">
<div class="tag"></div>
<button class="article__button--primary"></button>
<button class="article__button--success"></button>
</div>
</div>
通过 BEM 命名方式,模块层级关系简单清晰,而且 css 书写上也不必作过多的层级选择。
怎么用 ?
假设我们要实现这样的一个卡片功能:
根据上面的设计图,我们用 bem 方式来给对应 class
命名,如下所示:
<div class="card">
<img class="card__img" src="./img.jpg" alt="">
<div class="card__content">
<ul class="card__list">
<li class="card__item card__item--active">手机</li>
<li class="card__item">移动市场</li>
<li class="card__item">科技</li>
</ul>
<p class="card__desc">商化前端是一个很有活力的团队,能学到很多知识,你心动了吗?</p>
<a class="card__link" href="#">详细内容</a>
</div>
</div>
对应的 CSS 结构:
代码语言:javascript复制.card{
// 省略...
}
.card__img{
// 省略...
}
.card__content {
// 省略...
}
.card__list{
// 省略...
}
.card__item {
// 省略...
}
.card__item--active {
// 省略...
}
.card__link{
// 省略...
}
.card__link:hover{
// 省略...
}
从上面的代码可以看出,我们样式类没有一点嵌套关系,嵌套关系都以从命名的方式来代替。
这里刚开始使用 bem 的时候容易犯一个问题,就是把 ul
和 li
的样式写成 card__content__list
和 card__content__list__item
因为这样更能体现层级的关系。
这有悖BEM命名规范,BEM的命名中只包含三个部分,元素名只占其中一部分,所以不能出现多个元素名的情况。这样的约定可以防止当层级很深命名过长的问题。
上面我们每个样式都要写一遍 card
,如果 card 换成一个比较长的单词,这样也太冗长了,这也是大家不太喜欢 bem 的一个原因,但这个 sass
或 less
是很好的解决的,我们可以用 &
表示根元素,上面在 less 或 sass 中可以改成如下结构:
.card{
// 省略...
&__img{
// 省略...
}
&__content {
// 省略...
}
&__list{
// 省略...
}
&__item {
// 省略...
}
&__item--active {
// 省略...
}
&__link{
// 省略...
}
&__link:hover{
// 省略...
}
}
插件的使用
和 eslint
校验类似,stylelint
也有一个配置文件.stylelintrc.js
(还有其他格式的,这里以js
文件为例)。
module.exports = {};
为了让小伙伴编写符合 Bem 的规范,这里我们使用 stylelint-selector-bem-pattern 插件,它结合了插件 postcss-bem-linter 的规则,可用于校验 BEM
命名规范。
module.exports = {
plugins: [
'stylelint-selector-bem-pattern'
],
"rules": {
'plugin/selector-bem-pattern': {
// 选择Preset Patterns,支持suit和bem两种,默认suit规范;
// 不管哪种都需要手动指定,因为该插件未给源插件默认指定
'preset': 'bem',
/**
* 自定义模式规则
* 指定组合的选择器检查规则,其实就是指定class名称规则
* 支持正则字符串、返回正则的函数、包含2个选项参数的对象等写法
*/
componentSelectors: {
// 只初始的选择器规则(可以理解为外层的class规则)
initial: '^\.{componentName}(?:__[-a-z] )?(?:--[a-z] )?$',
// 可以理解为外层以内的选择器的规则,
// 如果不指定,则跟initial同样的规则,
// 注意这里配置的时候比上面少一个问号,
// 是希望内层就不应该只有componentName作为选择器了
combined: '^\.{componentName}(?:__[-a-z] )(?:--[a-z] )?$'
},
"utilitySelectors": "^\.u-[a-z] $",
ignoreSelectors: ['^\.el-', '/deep/', '>>>', '^\.icon-'],
ignoreCustomProperties: [],
}
}
}
配置完成后,为了能让 VsCode 给出错误提示,我们需要在 VsCode 中添加 stylelint
插件。
最后, 就是 git commit
校验
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{vue,ts,tsx,js,jsx}": "eslint --fix",
"*.{vue,css,less,sass,scss}": "stylelint --fix"
},
}
这里涉及到 husky 的使用,如果不懂的,可以自行谷歌了解理详细的信息。
实战
配置完成后,我们就需要动手验证一下了
首先,我们需要定义一个上下文,这样插件才知道对 CSS 进行校验。
比如我们有如下的 html
结构:
<div class="form form--theme-xmas">
<input class="form__input" />
<input class="form__submit form__submit--disabled" type="submit" />
</form>
对应的 css
要这样写:
/** @define formWrapper */
.formWrapper{
padding: 0 20px;
box-sizing: border-box;
}
.formWrapper--line{
display: none;
}
.formWrapper__form-item{
display: flex;
align-items: center;
margin-bottom: 20px;
}
这里 @define formWrapper
声明了一个 block formWrapper
, 表示样式必须是 formWrapper
开头,否则报错。
如果有多个 block,我们只要多个 @define
声明即可。
/** @define Foo */
.Foo {}
/** @define Bar */
.Bar {}
如果一个类不属于任何的 block,我们又要怎么做,才不会导致 styleint 报错呢?这里我们可以加 /** @end */
表示 block 的结束。
/** @define form */
.form{
display: flex;
}
.form--theme-blue{
text-align: center;
}
/** @end */
.isActive{
display: flex;
}
如果我们想忽略对某块样式进行校验,可以使用下面的语法来忽略:
代码语言:javascript复制/** @define MyComponent */
.MyComponent {
display: flex;
}
/* postcss-bem-linter: ignore */
.no-flexbox .Component {
display: block;
}
总结
BEM 最难的部分之一是明确作用域是从哪开始和到哪结束的,以及什么时候使用或不使用它。随着不断使用的经验积累,你慢慢就会知道怎么用,这些问题也不再是问题。技术无好坏,合适方最好。
参考: https://www.jianshu.com/p/54b... https://juejin.cn/post/684490... https://segmentfault.com/a/11... https://www.kancloud.cn/kancl... https://juejin.cn/post/688530...
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
交流
本文 GitHub https://github.com/qq44924588... 已收录,有一线大厂面试完整考点、资料以及我的系列文章。