BEM:用命名规范拯救你的 CSS 混乱

1,102 阅读4分钟

在前端开发中,CSS 是构建用户界面的重要工具,但随着项目规模的扩大,CSS 代码往往会变得混乱不堪:样式冲突、命名随意、维护困难……这些问题让开发者头疼不已。而 BEM(Block Element Modifier)作为一种 CSS 命名规范,正是为了解决这些问题而诞生的。本文将带你了解 BEM 的核心思想,并展示它如何拯救你的 CSS 混乱。

什么是 BEM?

BEM 是一种 CSS 命名方法论,由 Yandex 团队提出。它的名字来源于三个核心概念:

  1. Block(块) :独立的、可复用的组件或模块,比如 headermenubutton
  2. Element(元素) :Block 的组成部分,不能脱离 Block 单独存在,比如 menu 中的 itembutton 中的 icon
  3. Modifier(修饰符) :用于描述 Block 或 Element 的状态或外观变化,比如 button--disabledmenu__item--active

BEM 通过统一的命名规则,将 CSS 类名结构化,使得代码更清晰、更易维护。

为什么 CSS 会变得混乱?

在传统的 CSS 开发中,我们常常会遇到以下问题:

  1. 样式冲突:全局作用域导致类名冲突,一个组件的样式可能影响另一个组件。
  2. 命名随意:类名缺乏规范,比如 .btn.button.my-button 混用,难以理解。
  3. 维护困难:随着项目规模扩大,CSS 文件变得臃肿,修改一个样式可能会引发意想不到的问题。

这些问题不仅降低了开发效率,还增加了团队协作的难度。

BEM 如何拯救 CSS 混乱?

1. 清晰的命名规则

BEM 的命名规则非常简单:

  • Block:block-name
  • Element:block-name__element-name
  • Modifier:block-name--modifier-name 或 block-name__element-name--modifier-name 例如:
<div class="card">
  <img class="card__image" src="image.jpg" alt="Example">
  <div class="card__content">
    <h2 class="card__title">Card Title</h2>
    <p class="card__description">This is a card description.</p>
    <button class="card__button card__button--primary">Click Me</button>
  </div>
</div>

2. 模块化设计

BEM 将界面拆分为独立的 Block,每个 Block 都有自己的样式和逻辑。这种模块化设计使得代码更易于复用和维护。

例如,一个 button Block 可以在多个地方使用,而无需担心样式冲突:

.button { /* Block */ }
.button__icon { /* Element */ }
.button--disabled { /* Modifier */ }

3. 避免样式污染

BEM 的类名具有唯一性,避免了全局作用域下的样式冲突。例如,.card__title 只会影响 card Block 中的标题,而不会影响其他组件的标题。


4. 提高团队协作效率

BEM 的命名规范是统一的,团队成员可以快速理解代码结构,减少沟通成本。无论是新成员加入,还是老成员维护代码,都能轻松上手。


BEM 的实际应用

以下是一个完整的 BEM 示例:

HTML

<div class="card">
  <img class="card__image" src="image.jpg" alt="Example">
  <div class="card__content">
    <h2 class="card__title">Card Title</h2>
    <p class="card__description">This is a card description.</p>
    <button class="card__button card__button--primary">Click Me</button>
  </div>
</div>

CSS

.card { /* Block */ }
.card__image { /* Element */ }
.card__content { /* Element */ }
.card__title { /* Element */ }
.card__description { /* Element */ }
.card__button { /* Element */ }
.card__button--primary { /* Modifier */ }

通过这种结构化的命名方式,代码的可读性和可维护性大大提升。


BEM 的注意事项

  1. 避免过深的嵌套
    例如:block__element__subelement 是不推荐的,应该保持层级扁平。
  2. 不要滥用修饰符
    修饰符应该用于描述状态或外观的变化,而不是创建新的组件。
  3. 保持命名简洁
    类名应尽量简短且语义化,避免过长或过于复杂的命名。

使用js封装一个bem生成器

// BEM 的核心概念
// Block(块)
// 独立的、可复用的组件或模块。
// 例如:header、menu、button。
// 命名规则:block-name(全小写,单词用连字符分隔)。
// Element(元素)
// Block 的组成部分,不能脱离 Block 单独存在。
// 例如:menu 中的 item、button 中的 icon。
// 命名规则:block-name__element-name(用双下划线 __ 连接 Block 和 Element)。
// Modifier(修饰符)
// 用于描述 Block 或 Element 的状态或外观变化。
// 例如:button--disabled、menu__item--active。
// 命名规则:block-name--modifier-name 或 block-name__element-name--modifier-name(用双连字符 -- 表示修饰符)。

// 生成一个具体的bem命名规则
function generateBem(prefixName:string, blockName: string, elementName: string, modifierName: string) {
 if(blockName) {
   prefixName += `-${blockName}`
 }
 if(elementName) {
   prefixName += `__${elementName}`
 }
 if(modifierName) {
   prefixName += `--${modifierName}`
 }
 return prefixName
}

function createBem(prefixName: string) {
 // 生成一个 bolck (模块)
 const b = (blockName:string = "") => blockName ? generateBem(prefixName, blockName, "","") : prefixName
  // 生成一个 element (元素)
 const e = (elementName:string = "") => elementName ? generateBem(prefixName, "", elementName,""): ""
 // 生成 Modifier(修饰符)
 const m = (modifierName:string = "") => modifierName ? generateBem(prefixName, "", "", modifierName): ""
 // 生成一个 bolck (模块) element (元素)
 const be = (blockName:string = "", elementName:string = "") => blockName && elementName ? generateBem(prefixName, blockName, elementName,""): ""
 // 生成一个 bolck (模块) Modifier(修饰符)
 const bm = (blockName:string = "" ,modifierName:string = "") => blockName && modifierName ? generateBem(prefixName, blockName, "",modifierName): ""
 // 生成一个 bolck (模块) element (元素)  Modifier(修饰符)
 const bem = (blockName:string = "", elementName:string = "", modifierName:string = "") => blockName && elementName && modifierName ? generateBem(prefixName, blockName, elementName,modifierName): ""
 return {
   b,
   e,
   m,
   be,
   bm,
   bem
 }
}
// 声明一个bem函数
function useBem(className: string) {
 const prefixName = `o-${className}`
 // 调用bem返回一系列生成bem命名的方法
 return createBem(prefixName)
}

// ----
const { b, e, m, be, bm, bem  } = useBem("input")
console.log(b("box"), b(""))
console.log(e("item"), e(""))
console.log(m("focus"), e(""))
console.log(be("box", "label"))
console.log(bm("box", "focus"))
console.log(bem("box", "label", "checked"))