写点什么

CSS 特异性控制:层叠层 vs. BEM vs. 工具类

作者:qife122
  • 2025-09-03
    福建
  • 本文字数:5064 字

    阅读完需:约 17 分钟

CSS 层叠层 vs. BEM vs. 工具类:特异性控制

CSS 是不可预测的——而特异性往往是罪魁祸首。Victor Ayomipo 分析了样式为何不按预期生效,以及为什么理解特异性比依赖 !important 标志更好。


CSS 真的很狂野,而且棘手。但让我们具体谈谈特异性。


在编写 CSS 时,几乎不可能没有遇到过样式未按预期应用的挫折——那就是特异性。你应用了一个样式,它起作用了,然后你尝试用另一个样式覆盖它,但……什么也没发生,它直接忽略了你。再次强调,这就是特异性。


当然,可以选择使用 !important 标志,但像所有之前的开发者一样,这总是有风险且不鼓励的。完全理解特异性比走那条路要好得多,否则你最终会与自己重要的样式作斗争。

特异性基础

许多开发者以不同的方式理解特异性的概念。


特异性的核心思想是,浏览器使用的 CSS 层叠算法决定了当两个或多个规则匹配同一元素时,应用哪个样式声明。


想想看。随着项目的扩展,特异性挑战也会增加。假设开发者 A 添加了 .cart-button,然后按钮样式看起来可以用于侧边栏,但需要稍作调整。之后,开发者 B 添加了 .cart-button .sidebar,从那时起,任何未来应用于 .cart-button 的更改都可能被 .cart-button .sidebar 覆盖,就这样,特异性战争开始了。


我写 CSS 的时间足够长,见证了开发者用来管理 CSS 特异性战争的不同策略。


/* 传统方法 */#header .nav li a.active { color: blue; }
/* BEM 方法 */.header__nav-item--active { color: blue; }
/* 工具类方法 */.text-blue { color: blue; }
/* 层叠层方法 */@layer components { .nav-link.active { color: blue; }}
复制代码


所有这些方法反映了如何控制或至少维护 CSS 特异性的不同策略:


  • BEM:通过明确性来简化特异性。

  • 工具优先 CSS:通过保持原子性来绕过特异性。

  • CSS 层叠层:通过将样式组织成分层组来管理特异性。


我们将把三者放在一起,看看它们如何处理特异性。

我与特异性的关系

我过去曾以为我完全理解了 CSS 特异性。比如内联大于 ID,ID 大于类,类大于标签。但是,阅读 MDN 文档关于 CSS 层叠如何真正工作后,真是大开眼界。


我在一个客户提供的旧代码库中处理过一段代码,看起来像这样:


/* 遗留代码 */#main-content .product-grid button.add-to-cart {  background-color: #3a86ff;  color: white;  padding: 10px 15px;  border-radius: 4px;}
/* 此处有 100 行其他代码 */
/* 我的新 CSS */.btn-primary { background-color: #4361ee; /* 新品牌颜色 */ color: white; padding: 12px 20px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);}
复制代码


看这段代码,.btn-primary 类根本没有机会对抗之前编写的任何特异性选择器链。就特异性而言,CSS 给第一个选择器的特异性分数是 1, 2, 1:一个 ID 点,两个类点,一个元素选择器点。同时,第二个选择器的分数是 0, 1, 0,因为它只包含一个类选择器。


当然,我有一些选择:


  • 我可以在 .btn-primary 的属性上使用 !important 来覆盖更强选择器中声明的属性,但一旦这样做,就要准备到处使用它。所以,我宁愿避免。

  • 我可以尝试更具体,但个人认为,那只是对下一个开发者(甚至可能是我自己)残忍。

  • 我可以更改现有代码的样式,但那是在增加特异性问题:


#main-content .product-grid .btn-primary {  /* 直接编辑样式 */}
复制代码


最终,我最终从头重写了整个 CSS。


当嵌套被引入时,我尝试用它来控制特异性:


.profile-widget {  // ... 其他样式  .header {    // ... 标题样式    .user-avatar {      border: 2px solid blue;      &.is-admin {        border-color: gold; // 这变成了 .profile-widget .header .user-avatar.is-admin      }    }  }}
复制代码


就这样,我无意中创建了高特异性规则。这就是我们如何轻松自然地漂向特异性复杂性。


所以,为了节省自己很多这些问题,我有一个我一直遵守的原则:尽可能保持特异性低。如果选择器复杂性正在变成一个复杂的链,我会重新考虑整个事情。

BEM:原始系统

块-元素-修饰符(简称 BEM)已经存在很长时间了。它是一种编写 CSS 的方法论系统,迫使你使每个样式层次明确。


/* 块 */.panel {}
/* 依赖于块的元素 */.panel__header {}.panel__content {}.panel__footer {}
/* 改变块样式的修饰符 */.panel--highlighted {}.panel__button--secondary {}
复制代码


当我第一次体验 BEM 时,我认为它很棒,尽管有相反的意见认为它看起来丑陋。我对双连字符或下划线没有问题,因为它们使我的 CSS 可预测和简化。

BEM 如何处理特异性

看看这些例子。没有 BEM:


/* 特异性: 0, 3, 0 */.site-header .main-nav .nav-link {  color: #472EFE;  text-decoration: none;}
/* 特异性: 0, 2, 0 */.nav-link.special { color: #FF5733;}
复制代码


使用 BEM:


/* 特异性: 0, 1, 0 */.main-nav__link {  color: #472EFE;  text-decoration: none;}
/* 特异性: 0, 1, 0 */.main-nav__link--special { color: #FF5733;}
复制代码


你看到 BEM 如何使代码看起来可预测,因为所有选择器都是平等的,从而使代码更容易维护和扩展。如果我想在 .main-nav 中添加一个按钮,我只需添加 .main-nav__btn,如果我需要一个禁用按钮(修饰符),.main-nav__btn--disabled。特异性很低,因为我不必增加它或与层叠斗争;我只是写一个新类。


BEM 的命名原则确保组件生活在隔离中,这对于 CSS 的一部分,即特异性部分,它起作用了,即 .card__title 类永远不会意外地与 .menu__title 类冲突。

BEM 的不足之处

我喜欢 BEM 的想法,但它并不完美,很多人注意到了:


  • 类名可能变得非常长。


<div class="product-carousel__slide--featured product-carousel__slide--on-sale">  <!-- 哎呀 --></div>
复制代码


  • 可重用性可能不被优先考虑,这有点违背原生 CSS 意识形态。卡片内的按钮应该是 .card__button 还是重用全局 .button 类?对于前者,样式被复制,对于后者,BEM 严格模型被打破。

  • 软件开发中的一个核心痛苦开始成为现实——命名事物。我相信你已经知道那种挫折。


BEM 很好,但有时你可能需要灵活处理。混合系统(可能对核心组件使用 BEM,但在其他地方使用更简单的类)仍然可以保持所需的低特异性。


/* 没有 BEM 的基础按钮 */.button {  /* 按钮样式 */}
/* 带有 BEM 的组件特定按钮 */.card__footer .button { /* 小覆盖 */}
复制代码

工具类:通过避免处理特异性

这也称为原子 CSS。整体上,它避免特异性。


<button class="bg-red-300 hover:bg-red-500 text-white py-2 px-4 rounded">  一个按钮</button>
复制代码


工具优先类背后的想法是,每个工具类具有相同的特异性,即一个类选择器。每个类是一个微小的 CSS 属性,具有单一目的。


p-2?填充,仅此而已。text-red?文本颜色红色。text-center?文本对齐。就像乐高如何工作,但用于样式。你堆叠类直到获得所需的外观。

工具类如何处理特异性

工具类不解决特异性,而是将 BEM 的低特异性意识形态推向极端。几乎所有工具类都具有相同的最低可能特异性水平(0, 1, 0)。因此,覆盖变得容易;如果需要更多填充,将 .p-2 增加到 .p-4


另一个例子:


<button class="bg-orange-300 hover:bg-orange-700">  这可以悬停</button>
复制代码


如果添加另一个类 hover:bg-red-500,顺序对 CSS 决定使用哪个很重要。所以,即使工具类避免特异性,CSS 层叠的其他部分介入,即出现顺序,最后声明的匹配选择器获胜。

工具类的权衡

工具类最常见的问题是它们使代码看起来丑陋。坦白说,我同意。但能够想象组件的样子而不看到它渲染是无价的。


还有可重用性的论点,你每次重复自己。但一旦发现重复发生,只需将该部分变成可重用组件。它在特异性方面也有其真正的限制:


  • 如果你的品牌颜色改变,这是一个全局变化,并且你深入代码库,你不能只更改一个并让其他跟随像原生 CSS。

  • 由于原子工具类的行为,原生 CSS 中自然发生的父子关系被排除。


有些人认为 HTML 部分应保留为标记,CSS 部分用于样式。因为现在,有更多标记要扫描,如果你决定清理:


<!-- 太长 --><div class="p-4 bg-yellow-100 border border-yellow-300 text-yellow-800 rounded">
<!-- 更好? --><div class="alert-warning">
复制代码


就这样,我们最终编写了 CSS。生命循环。


在我使用工具类的经验中,它们最适合:


  • 速度:编写标记,样式化它,并迅速看到结果。

  • 可预测性:工具类完全按照它所说的做。

层叠层:通过设计处理特异性

现在,这变得有趣。BEM 提供结构,工具类获得速度,而 CSS 层叠层给我们一些至关重要的东西:控制。


无论如何,层叠层(@layer)分组样式并声明组的顺序,无论这些规则的特异性分数如何。


看一组独立的规则集:


button {  background-color: orange; /* 特异性: 0, 0, 1 */}
.button { background-color: blue; /* 特异性: 0, 1, 0 */}
#button { background-color: red; /* 特异性: 1, 0, 0 */}
/* 无论什么,按钮是红色的 */
复制代码


但使用 @layer,假设我想优先考虑 .button 类选择器。我可以塑造特异性顺序应该如何:


@layer utilities, defaults, components;
@layer defaults { button { background-color: orange; /* 特异性: 0, 0, 1 */ }}
@layer components { .button { background-color: blue; /* 特异性: 0, 1, 0 */ }}
@layer utilities { #button { background-color: red; /* 特异性: 1, 0, 0 */ }}
复制代码


由于 @layer 的工作方式,.button 会赢,因为组件层是最高优先级,即使 #button 有更高的特异性。因此,在 CSS 甚至检查通常的特异性规则之前,层顺序首先被尊重。


你只需尊重 W3C 的人,因为现在可以有目的地用简单类覆盖 ID 选择器,甚至不使用 !important。迷人。

层叠层的细微差别

当我们谈论 CSS 层叠层时,有一些值得指出的事情:


  • 特异性仍然是游戏的一部分。

  • !important@layer 中的行为与预期不同(它们反向工作!)。

  • @layer 不是选择器特定的,而是样式属性特定的。


@layer base {  .button {    background-color: blue;    color: white;  }}
@layer theme { .button { background-color: red; /* 这里没有颜色属性,所以基础层的白色仍然适用 */ }}
复制代码


@layer 可能容易被滥用。我确信有一个开发者有超过 20+ 层声明,已经变成怪物。

比较三者

现在,对于 TL;DR 的人,这里是三者的并排比较:BEM、工具类和 CSS 层叠层。



三者中,每个都有其甜蜜点:


  • BEM 最适合

  • 有需要一致性的清晰设计系统,

  • 有对 CSS 有不同哲学的团队(BEM 可以是中间立场),

  • 样式不太可能在组件之间泄漏。

  • 工具类最适合

  • 你需要快速构建,如原型或 MVP,

  • 使用基于组件的 JavaScript 框架如 React。

  • 层叠层最有效

  • 处理需要完全特异性控制的遗留代码库,

  • 你需要集成第三方库或来自不同源的样式,

  • 处理大型、复杂应用程序或具有长期维护的项目。


如果我必须选择或排名它们,我会选择工具类与层叠层而不是使用 BEM。但那只是我!

它们相交的地方(它们如何一起工作)

三者中,层叠层应被视为协调者,因为它可以与其他两种策略一起工作。@layer 是 CSS 层叠架构的基本原则,不像 BEM 和工具类,它们是控制层叠行为的方法论。


/* 层叠层 + BEM */@layer components {  .card__title {    font-size: 1.5rem;    font-weight: bold;  }}
/* 层叠层 + 工具类 */@layer utilities { .text-xl { font-size: 1.25rem; } .font-bold { font-weight: 700; }}
复制代码


另一方面,使用 BEM 与工具类只会最终冲突:


<!-- 这感觉错误 --><div class="card__container p-4 flex items-center">  <p class="card__title text-xl font-bold">似乎有问题</p></div>
复制代码


我摊牌:我是一个工具优先的开发者。而且大多数工具类框架在幕后使用 @layer(例如,Tailwind)。所以,那两个已经在袋子里一起了。


但是,我讨厌 BEM 吗?一点也不!我用了很多,如果必要,仍然会用。我只是发现命名事物是一种 exhausting 练习。


也就是说,我们都不同,你可能对什么感觉最好有相反的想法。这真的不重要,这就是这个网页开发空间的美。多条路线可以导致同一目的地。

结论

所以,当谈到比较 BEM、工具类和 CSS 层叠层时,对于控制层叠中的特异性,是否有真正的“赢家”方法?


首先,CSS 层叠层可以说是我们多年来获得的最强大的 CSS 功能。它们不应与 BEM 或工具类混淆,后者是策略而不是 CSS 功能集的一部分。


这就是为什么我喜欢将 BEM 与层叠层结合或工具类与层叠层结合的想法。无论哪种方式,想法是保持特异性低,并利用层叠层为那些样式设置优先级。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)公众号二维码


办公AI智能小助手


用户头像

qife122

关注

还未添加个人签名 2021-05-19 加入

还未添加个人简介

评论

发布
暂无评论
CSS 特异性控制:层叠层 vs. BEM vs. 工具类_CSS_qife122_InfoQ写作社区