听说过 BEM 吗?聊聊如何落地 BEM 规范

前言

哈喽大家好!我是 嘟老板 。随着时代的发展,大家对于前端应用的体验和美观要求越来越高,相应的,前端程序中的 CSS 应用也越来越多,越来越复杂。如何合理的组织 CSS 代码,提高代码的可读性和可维护性至关重要。今天我们就聊聊组织 CSS 代码的规范之一- BEM

阅读本文您将收获:

  1. BEM 规范定义及优势。
  2. 如何在项目中落地 BEM 规范。

什么是 BEM

BEMCSS 类选择器命名规范,可划分为以下三部分:

  • BBlock ,有意义的独立实体,如 menuheadercontainerinput 等。
  • EElementBlock 的一部分,没有独立含义,在语义上和 Block 有关联,如 menu itemheader titlecontainer content 等。
  • MModifierBlockElement 上的标志性描述,通常用于改变外观和行为,如 disabledcheckedhover 等。

其中 BlockElement 之间使用 双下划线 __ 连接;Block/ElementModifier 之间使用 双横线 -- 连接;

例如:

css 复制代码
.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}

为什么使用 BEM

模块化

应用 BEM 规范,Block 下的所有样式作为一个整体,不会与其他模块产生耦合或交集;

可重用

每个 Block 下的 CSS 都是一个整体,可以在项目内自由使用,或者直接迁移到新项目,不会有代码缺失或遗漏问题。

结构清晰

通过选择器名称,便可以很清晰的知道 Block 下的所有 CSS,明确选择器间的从属关系,了解不同元素之间的关联等。

高度契合预处理框架

常用的 Sass/Lesscss 预处理框架,都支持嵌套定义。

Sass:

scss 复制代码
.parent {
    &__child {}
    &--disabled {}
}

编译成 css 的结果如下:

css 复制代码
.parent .parent__child {}
.parent .parent--disabled {}

是不是很契合 BEM,遵守命名规范的同时,减少代码量,又限定了作用域,避免样式污染,简直是一举多得。

落地 BEM

那有的小伙伴就想说了,按照 BEM 规范命名好麻烦呀,类名又臭又长,显得多臃肿!

诚然,要记住规范,还要写超长的类名,确实不够优雅。我们可以封装符合规范的生成工具,既可遵循 BEM,又能减轻心智负担。

下面是基于 Vue3 + Sass 的封装思路,供参考。

useBem composition api

javascript 复制代码
/**
 * 生成 class 类名
 *
 * 规则:[block]-[blockSuffix]__[element]--[modifier]
 * @param block 块名称
 * @param blockSuffix 块名称后缀
 * @param element 元素名称
 * @param modifier 变更标志名称
 * @returns
 */
function genBemClass(
  block: string,
  blockSuffix?: string,
  element?: string,
  modifier?: string
) {
  let clsName = block
  if (blockSuffix) clsName += `-${blockSuffix}`
  if (element) clsName += `__${element}`
  if (modifier) clsName += `--${modifier}`
  return clsName
}

export function useBem(block: string) {
  const b = (blockSuffix?: string) => {
    return genBemClass(block, blockSuffix)
  }

  const e = (element: string) => {
    return genBemClass(block, '', element)
  }

  const m = (modifier: string) => {
    return genBemClass(block, '', modifier)
  }

  const be = (blockSuffix: string, element: string) => {
    return genBemClass(block, blockSuffix, element)
  }

  const bm = (blockSuffix: string, modifier: string) => {
    return genBemClass(block, blockSuffix, '', modifier)
  }

  const em = (element: string, modifier: string) => {
    return genBemClass(block, '', element, modifier)
  }

  const bem = (blockSuffix: string, element: string, modifier: string) => {
    return genBemClass(block, blockSuffix, element, modifier)
  }

  return {
    b,
    e,
    m,
    be,
    bm,
    em,
    bem
  }
}

genBemClass 函数定义了 BEM 规范生成类名的规则:

bash 复制代码
[block]-[blockSuffix]__[element]--[modifier]

其中:

  • block :块名称,如 header
  • blockSuffix :块名称后缀,用于定义更语义化的块名称,如 header-content
  • element :元素名称,如 header__title
  • modifier :标志名称, 如 header__title--hover

useBem 组合 api 根据 BEM 各部分的多种组合方式,定义了如下几个函数:

  • buseBem 的参数 block + 后缀 blockSuffix 按规则生成 class 名称。
  • euseBem 的参数 block + 元素名 element 按规则生成 class 名称。
  • museBem 的参数 block + 标志名 modifier 按规则生成 class 名称。
  • beuseBem 的参数 block + 后缀 blockSuffix 和元素名 element 按规则生成 class 名称。
  • bmuseBem 的参数 block + 后缀 blockSuffix 和标志名 modifier 按规则生成 class 名称。
  • emuseBem 的参数 block + 元素名 element 和标志名 modifier 按规则生成 class 名称。
  • bemuseBem 的参数 block + 后缀 blockSuffix + 元素名 element + 标志名 modifier 按规则生成 class 名称。

vue 组件中,按照如下方式应用即可:

html 复制代码
<template>
    <div :class="ub.b()">
        <div :class="ub.b('content')"></div>
        <div :class="ub.e('title')"></div>
    </div>
</template>
<script setup>
const ub = useBem('header')
</script>

Sass mixin

生成类名称的组合函数搞定了,接下来我们使用 Sass @mixin 来创建类选择生成工具。mixin 允许我们像 js 函数一样传递参数。

sass 复制代码
// block 与 element 分隔符
$element-separator: '__' !default;
// block/element 与 modifier 分隔符
$modifier-separator: '--' !default;

@mixin b($block) {
  .#{$block} {
    @content;
  }
}

@mixin e($element) {
  $selector: &;
  $currentSelector: '';
  @each $unit in $element {
    $currentSelector: #{$currentSelector +
      $selector +
      $element-separator +
      $unit +
      ','};
  }

  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

@mixin m($modifier) {
  $selector: &;
  $currentSelector: '';
  @each $unit in $modifier {
    $currentSelector: #{$currentSelector +
      $selector +
      $modifier-separator +
      $unit +
      ','};
  }

  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

定义了以下三个 mixin

  • b :创建 block 样式块。
  • e :创建 element 样式块,可内嵌 b mixin 中。
  • m :创建 modifier 样式块,可内嵌 be mixin 中。

可能有的小伙伴不太熟悉 Sass 语法,简单介绍下:

  • @each... in... :类似 js 的遍历,in 后面是要遍历的 list@each 后是 list 的每个元素,大括号{} 中的代码块会为 list 的每个元素依次执行。
  • @content :类似 vue 的插槽,允许我们定义样式块替换 @content,比如:
css 复制代码
@mixin hover {
  &:hover {
    @content;
  }
}

.button {
  border: 1px solid black;
  @include hover {
    border-width: 2px;
  }
}

编译后的 css 如下:

css 复制代码
.button {
  border: 1px solid black;
}
.button:hover {
  border-width: 2px;
}

使用 { border-width: 2px; } 样式块替换 @content

  • @at-root:将指定选择器放到文档根路径下,而不是保持定义时的嵌套关系。比如:
scss 复制代码
.container {
  @at-root .btn { background-color: blue; }
}

编译为 css 后的结果为:

css 复制代码
.container {}
.btn { background-color: blue; }

测试应用

我们来创建个 TestButton 组件应用一下,代码如下:

html 复制代码
<template>
  <button :class="ub.b()">
    <span :class="[ub.e('label'), disabled ? ub.em('label', 'disabled') : '']">Test Button</span>
  </button>
</template>
<script setup>
import { useBem } from '@/hooks'

const ub = useBem('test-button')

defineProps({
  disabled: {
    type: Boolean,
    default: false
  }
})
</script>
<style lang="scss">
@use '@/styles/mixins.scss' as *;

@include b(test-button) {
  background: green;

  @include e(label) {
    color: red;

    @include m(disabled) {
      color: gray;
    }
  }
}
</style>

使用 useBem 返回的函数创建相关 class 类名,通过 Vue 语法绑定到相应元素 class 属性上。

引入 Sass mixins ,生成相应的 Sass 代码块,并分别设置样式。当组件属性 disabledtrue 时,按钮文本显示为红色;为 false 时,按钮文本显示为灰色。

在页面中引入 TextButton,看下效果:

html 复制代码
<template>
  <test-button />
  <test-button disabled />
</template>

生成的类名完全符合 BEM 规范。

结语

本文重点介绍了 BEM 规范的相关内容及在 Vue/Sass 项目中的应用方法,旨在帮助同学们加深对于 BEM 规范的应用理解。希望对您有所帮助。相关代码已上传至 GitHub,欢迎 star

如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。

技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。


往期推荐

相关推荐
燃先生._.3 分钟前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖1 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235241 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240252 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar2 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人3 小时前
前端知识补充—CSS
前端·css
GISer_Jing3 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245523 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v3 小时前
webpack最基础的配置
前端·webpack·node.js