1,什么是BEM

对于一个前端开发团队来说,随着项目的规模越来越大,良好的代码规范越显得重要。
在 CSS 命名方面,经常会遇到的问题:

  • 绞尽脑汁的去想一个 class,还得和其他类似的 class 做区分。
  • 修改旧代码时,得去仔细确认每个 class 的作用,是修改、删除,还是添加一个新的 class 覆盖。
  • 协作开发时 class 命名的冲突。

BEM 可以解决这些问题,让代码更容易阅读和控制,规范团队的代码风格。

1.1,介绍

BEM 是一种CSS命名规范。由 B(block)模块,E(element)元素,M(modifier)修饰符组成。
书写规范:

  • 连接 element 用 __双下划线
  • 连接 modifier 用 --双中划线
  • 1个 class 中,B、E、M 尽量都只有1个(比如:尽量避免block__el1__el2

具体表现如下:

1
2
3
4
.block {}
.block__element {}
.block__element--modifier {}
.block--modifier {}

  • 在前端项目中,一般是由多个组件构成的,组件就是一个模块 block。
  • element 表示 block 内更细粒度的元素,一般以布局或功能区分这些元素。
  • modifier 用于标记 block 或 element 的不同版本或状态

1.2,举例

用例:弹出框组件 dialog

1
2
3
4
5
6
7
8
<div class="dialog center">
<div class="header">
<div class="title"></div>
<div class="closebtn"></div>
</div>
<div class="body"></div>
<div class="footer"></div>
</div>

这样的命名方式,在阅读时并不能确定:

  • center是一个通用的 class,还是只对 dialog 生效。
  • body等是否也只在 dialog 中用到。如果body 中又嵌套了一个组件,也有类似body 的结构,命名就有点麻烦了。

BEM改造:

1
2
3
4
5
6
7
8
<div class="dialog dialog--center">
<div class="dialog__header">
<div class="dialog__title"></div>
<div class="dialog__closebtn"></div>
</div>
<div class="dialog__body"></div>
<div class="dialog__footer"></div>
</div>

1.4,总结

可以看到通过BEM规范化后,代码更易阅读和控制:

  • 增加了代码的自解释性,能够直观的看到层级和依赖关系。
  • class 单一职责。
  • 避免了命名污染(污染外层或公共的同名 class)。

2.使用用法

这里用预编译器 sass 举例,其他预编译器用法类似。

安装 sass:

1
npm install sass

使用sass 最小单元复刻一个bem 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$block-sel: "-" !default;
$element-sel: "__" !default;
$modifier-sel: "--" !default;
$namespace:'xm' !default;
@mixin bfc {
height: 100%;
overflow: hidden;
}

//混入
@mixin b($block) {
$B: $namespace + $block-sel + $block; //变量
.#{$B}{ //插值语法#{}
@content; //内容替换
}
}

@mixin flex {
display: flex;
}

@mixin e($element) {
$selector:&;
@at-root {
#{$selector + $element-sel + $element} {
@content;
}
}
}

@mixin m($modifier) {
$selector:&;
@at-root {
#{$selector + $modifier-sel + $modifier} {
@content;
}
}
}

在vite.config.ts 中引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
scss: {
additionalData: "@import './src/bem.scss';"
}
}
}
})

Vue 组件用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div class="xm-wraps">
<div>
<Menu></Menu>
</div>
<div class="xm-wraps__right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>

<script lang="ts" setup>
import { ref, reactive } from "vue"
import Menu from './Menu/index.vue'
import Content from './Content/index.vue'
import Header from './Header/index.vue'
</script>

<style lang="scss" scoped>
@include b('wraps'){
@include bfc;
@include flex;
@include e(right){
flex:1;
display: flex;
flex-direction: column;
}
}
</style>