BEM 101

Avatar of Robin Rendle
Robin Rendle 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

以下是来自客座博主 Joe Richardson、Robin Rendle 和 CSS-Tricks 团队的合作文章。Joe 想要写一篇关于 BEM 的文章,我们很喜欢,而且几乎每个人都有关于 BEM 的想法和意见,所以我们觉得我们可以一起合作。

块(Block)、元素(Element)、修饰符(Modifier) 方法论(通常称为 BEM)是 HTML 和 CSS 中类名的流行命名约定。它由 Yandex 团队开发,旨在帮助开发人员更好地理解给定项目中 HTML 和 CSS 之间的关系。

以下是一个使用 BEM 风格的 CSS 开发人员可能编写的示例

/* Block component */
.btn {}

/* Element that depends upon the block */ 
.btn__price {}

/* Modifier that changes the style of the block */
.btn--orange {} 
.btn--big {}

在这种 CSS 方法论中,是新组件的顶级抽象,例如按钮:.btn { }。这个块应该被认为是父元素。子元素或元素可以放置在其中,它们用块名称后的两个下划线表示,例如 .btn__price { }。最后,修饰符可以操作块,以便我们可以对特定组件进行主题化或样式化,而不会对完全无关的模块造成影响。这可以通过在块名称后添加两个连字符来完成,例如 btn--orange

标记可能如下所示

<a class="btn btn--big btn--orange" href="https://css-tricks.org.cn">
  <span class="btn__price">$9.99</span>
  <span class="btn__text">Subscribe</span>
</a>

如果另一位开发人员编写了此标记,而我们不熟悉 CSS,我们仍然应该对哪些类负责哪些内容以及它们如何相互依赖有一个很好的了解。然后,开发人员可以构建自己的组件并根据自己的意愿修改现有块。无需编写太多 CSS,开发人员可能能够仅通过更改标记中的类来创建许多不同的按钮组合。

查看 CodePen 上由 CSS-Tricks (@css-tricks) 创建的 BEM 示例

起初,这种语法可能看起来比简单地为每种类型的按钮创建一个新类要慢,但由于我们将要讨论的几个原因,事实并非如此。

为什么我们应该考虑 BEM?

  1. 如果我们要创建一个组件的新样式,我们可以很容易地看到哪些修饰符和子元素已经存在。我们甚至可能意识到我们根本不需要编写任何 CSS,因为已经存在一个可以满足我们需求的预先存在的修饰符。
  2. 如果我们正在阅读标记而不是 CSS,我们应该能够快速了解哪些元素依赖于另一个元素(在前面的示例中,我们可以看到 .btn__price 依赖于 .btn,即使我们还不知道它是什么)。
  3. 设计师和开发人员可以一致地命名组件,以便团队成员之间更容易沟通。换句话说,BEM 为项目中的每个人提供了一种他们可以共享的声明式语法,这样他们就可以站在同一立场上了。

Harry Roberts 发现 使用像 BEM 这样的语法时,在提高开发人员信心方面另一个关键优势。

这是我们最终得到臃肿的代码库,充满了遗留代码和未知 CSS 的主要原因,我们不敢去碰它们。我们缺乏能够使用和修改现有样式的信心,因为我们害怕 CSS 全局操作和泄漏性质的后果。几乎所有与 CSS 规模相关的问题都归结为信心(或缺乏信心):人们不再知道事物是如何运作的。人们不敢进行更改,因为他们不知道影响范围有多大。

同样,Philip Walton 认为 如果足够多的开发人员坚持 BEM 原则,这个问题可以得到解决。

虽然 100% 可预测的代码可能永远无法实现,但了解您在选择约定时所做的权衡取舍很重要。如果您遵循严格的 BEM 约定,您将能够在未来更新和添加 CSS,并且完全有信心您的更改不会产生副作用。

因此,如果开发人员能够更有信心地参与项目,那么他们一定会对这些视觉组件的使用方式做出更明智的决定。这种方法论可能不是解决所有这些问题的完美解决方案,但它确实为开发人员提供了一种标准,使他们能够在未来编写更好、更易于维护的代码。

BEM 的另一个巧妙之处在于所有内容都是类,并且没有任何内容是嵌套的。这使得 CSS 特定性非常平坦和低,这是一个好主意。这意味着您不会因特定性问题而互相争斗。

让我们来看看 BEM 的一些问题…

BEM CSS 的问题

当然,如果您违反了 BEM 规则,没有人会强迫您。您仍然可以编写这样的 CSS 选择器

.nav .nav__listItem .btn--orange {
  background-color: green;
}

看起来它包含 BEM 的一部分,但它不是 BEM。它有嵌套选择器,而且修饰符甚至不能准确地描述正在发生的事情。如果我们这样做,我们将破坏对 BEM 非常有帮助的特定性平坦度。

一个块(如 .nav)永远不应该覆盖另一个块或修饰符(如 .btn--orange)的样式。否则,这将使我们几乎不可能阅读 HTML 并理解此组件的作用;在这个过程中,我们必然会严重损害另一位开发人员对代码库的信心。这对 HTML 也是如此:如果你看到了以下标记,你会期望什么?

<a class="btn" href="https://css-tricks.org.cn">
  <div class="nav__listItem">Item one</div>
  <div class="nav__listItem">Item two</div>
</a>

这里可能发生的情况是,完全无关的块中的一个元素具有开发人员所需的代码,但子元素不需要 .nav 类作为父元素。这会导致一个非常混乱和不一致的代码库,应该不惜一切代价避免。因此,我们可以总结这些问题:

  1. 永远不要在无关的块中覆盖修饰符。
  2. 避免在子元素可以独立存在时创建不必要的父元素。

更多 BEM 实践示例

手风琴演示

查看 CodePen 上由 CSS-Tricks (@css-tricks) 创建的 BEM 手风琴

在这个示例中,有一个块,两个元素和一个修饰符。在这里,我们创建了一个 .accordion__copy–open 修饰符,它让我们知道我们不应该在另一个块或元素上使用它。

导航演示

查看 CodePen 上由 CSS-Tricks (@css-tricks) 创建的 BEM 菜单

此导航演示包含 1 个块,6 个元素和 1 个修饰符。甚至完全可以创建没有修饰符的块。在未来的某个时刻,开发人员始终可以添加(或绑定到)新的修饰符,只要块保持一致即可。

不喜欢 BEM

也许您不喜欢双下划线或双连字符。没关系,使用其他一些独特且一致执行的符号。

以下是另一种观点

最后三个选择器都具有不同的特定性级别。它们要么需要父元素,要么不需要。如果没有规则,它们不会像上面的规则那样明确

这个微不足道且孤立的示例是否让您感觉良好,并且永远不会给您带来麻烦?也许吧。但是,项目中包含的 CSS 越多,这类小问题积累得越多,您遇到的特定性和复杂性问题也就越多。

这里不是要挑剔 Samuel,而是他的观点得到了很多人的认可,所以这是一个很好的例子。他们看到 BEM,就完全拒绝了它。如果您想不喜欢 BEM,那完全没问题,但我认为很难争辩说有一套规则可以帮助理解并有助于保持 CSS 可维护性是一个坏主意。

在 SMACSS 方法论中,您可能会发现一个包含三个字母的 CSS 类名。修饰符随后使用连字符跟随模块名称。

/* Example Module */
.btn { }

/* Modifier of the btn class */
.btn-primary { }

/* Btn Module with State */
.btn.is-collapsed { }

这仅仅是针对同一类问题的一种不同的命名方法。它非常相似,但您只是更明确地描述依赖关系并保持特定性更平坦。

OOCSS 中,块同样通用。

/* Example Module */
.mod { }

/* Part of Module */
.inner { }

/* Talk Module */
.talk { }

/* Variation of part inside Module */
.talk .inner { }

因此,你将在 HTML 中使用多个类来进行变体。内部部分没有像依赖项那样命名,因此不太清楚,但可能更可重用。BEM 会使用 .mod__inner.mod--talk 以及 .mod--talk__inner

这些只是方法论上的变体。请记住,这里没有人强迫你,这些都是自我强加的规则,它们的价值来自遵循它们。

Sass 和 BEM

对于那些使用 Sass 并喜欢使用嵌套来限定样式的人,你仍然可以使用嵌套格式编写代码,但可以使用 @at-root 获取不嵌套的 CSS。

.block {
  @at-root #{&}__element {
  }
  @at-root #{&}--modifier {
  }
}

它会为你生成

.block {
}
.block__element {
}
.block--modifier {
}

你可以变得尽可能抽象!看看 Danield Guillan 的 BEM 构造器 或 Anders Schmidt Hansen 的 表达力强的 BEM

总结

总结来说,我认为,尽管 BEM 无法解决我们所有的问题,但它对于构建可扩展和可维护的界面非常有用,团队中的每个人都应该清楚地了解如何改进这些界面。这是因为,大量的前端开发不仅仅是关于解决短期内一个小问题的巧妙技巧;我们需要在开发人员之间达成一致、承诺和有约束力的社会契约,以便我们的代码库能够随着时间的推移而适应。

通常,我认为 BEM 是对 Nicolas Gallagher 的问题的答案

进一步阅读