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 特异性战争的不同策略。
|
|
所有这些方法反映了如何控制或至少维护 CSS 特异性的不同策略:
- BEM:通过明确性来简化特异性。
- 工具优先 CSS:通过保持原子性来绕过特异性。
- CSS 层叠层:通过将样式组织成分层组来管理特异性。
我们将把这三者放在一起,看看它们如何处理特异性。
我与特异性的关系
我过去曾以为自己完全理解了 CSS 特异性。比如内联样式大于 ID,ID 大于类,类大于标签。但是,阅读 MDN 文档关于 CSS 层叠如何真正工作后,我大开眼界。
有一个我在客户提供的旧代码库中处理的代码,看起来像这样:
|
|
看这段代码,.btn-primary
类根本没有机会对抗之前编写的任何特异性选择器链。就特异性而言,CSS 给第一个选择器打分为 1, 2, 1:ID 一点,两个类两点,元素选择器一点。同时,第二个选择器得分为 0, 1, 0,因为它只包含一个类选择器。
当然,我有一些选择:
- 我可以在
.btn-primary
的属性上使用!important
来覆盖更强选择器中声明的属性,但一旦这样做,就要准备好到处使用它。所以,我宁愿避免它。 - 我可以尝试更具体,但个人认为,那只是对下一个开发者(甚至可能是我自己)的残忍。
- 我可以更改现有代码的样式,但那是在增加特异性问题:
|
|
最终,我最终从头重写了整个 CSS。
当嵌套被引入时,我尝试通过这种方式来控制特异性:
|
|
就这样,我无意中创建了高特异性规则。这就是我们如何轻松自然地漂向特异性复杂性。
所以,为了节省自己很多这些问题,我有一个我一直遵守的原则:尽可能保持特异性低。如果选择器复杂性正在变成一个复杂的链,我会重新思考整个事情。
BEM:原始系统
块-元素-修饰符(简称 BEM)已经存在很长时间了。它是一种编写 CSS 的方法论系统,迫使你使每个样式层次明确。
|
|
当我第一次体验 BEM 时,我认为它很棒,尽管有相反的意见认为它看起来很丑。我对双连字符或下划线没有问题,因为它们使我的 CSS 可预测和简化。
BEM 如何处理特异性
看看这些例子。没有 BEM:
|
|
使用 BEM:
|
|
你看到 BEM 如何使代码看起来可预测,因为所有选择器都是平等的,从而使代码更易于维护和扩展。如果我想在 .main-nav
中添加一个按钮,我只需添加 .main-nav__btn
,如果我需要一个禁用按钮(修饰符),.main-nav__btn--disabled
。特异性很低,因为我不必增加它或与层叠斗争;我只是写一个新类。
BEM 的命名原则确保组件生活在隔离中,这对于 CSS 的一部分,即特异性部分,它是有效的,即 .card__title
类永远不会意外地与 .menu__title
类冲突。
BEM 的不足
我喜欢 BEM 的想法,但它并不完美,很多人注意到了:
- 类名可能变得非常长。
|
|
- 可重用性可能不被优先考虑,这有点违背原生 CSS ideology。卡片内的按钮应该是
.card__button
还是重用全局.button
类?对于前者,样式被重复,对于后者,BEM 严格模型被打破。 - 软件开发中的一个核心痛点开始成为现实——命名事物。我相信你已经知道那种挫败感。
BEM 很好,但有时你可能需要灵活处理它。混合系统(可能对核心组件使用 BEM,但在其他地方使用更简单的类)仍然可以保持所需的低特异性。
|
|
工具类:通过避免处理特异性
这也称为原子 CSS。总的来说,它避免了特异性。
|
|
工具优先类背后的想法是,每个工具类具有相同的特异性,即一个类选择器。每个类是一个微小的 CSS 属性,具有单一目的。
p-2
?填充,仅此而已。text-red
?文本颜色红色。text-center
?文本对齐。就像乐高积木一样,但用于样式。你将类堆叠在一起,直到获得所需的外观。
工具类如何处理特异性
工具类不解决特异性,而是将 BEM 的低特异性 ideology 推向极致。几乎所有工具类都具有相同的最低可能特异性水平(0, 1, 0)。因此,覆盖变得容易;如果需要更多填充,将 .p-2
提升到 .p-4
。
另一个例子:
|
|
如果添加另一个类 hover:bg-red-500
,顺序对 CSS 决定使用哪个很重要。所以,即使工具类避免了特异性,CSS 层叠的其他部分也会介入,即出现顺序,最后声明的匹配选择器获胜。
工具类的权衡
工具类最常见的问题是它们使代码看起来丑陋。坦白说,我同意。但能够在不看到渲染的情况下想象组件的外观是无价的。
还有关于可重用性的论点,即你每次都重复自己。但一旦发现重复发生,只需将该部分转换为可重用组件。它在特异性方面也有其真正的局限性:
- 如果你的品牌颜色发生变化,这是一个全局变化,并且你深入代码库,你不能只更改一个并让其他像原生 CSS 一样跟随。
- 由于原子工具类的行为方式,原生 CSS 中自然发生的父子关系被排除在外。
一些人认为 HTML 部分应该保留为标记,CSS 部分用于样式。因为现在有更多的标记要扫描,如果你决定清理:
|
|
就这样,我们最终编写了 CSS。生命循环。
在我使用工具类的经验中,它们最适合:
- 速度:编写标记,样式化它,并快速看到结果。
- 可预测性:工具类完全按照它所说的做。
层叠层:通过设计处理特异性
现在,这变得有趣了。BEM 提供结构,工具类获得速度,而 CSS 层叠层给我们一些至关重要的东西:控制。
无论如何,层叠层(@layers
)将样式分组,并声明组的顺序,无论这些规则的特异性分数如何。
看一组独立的规则集:
|
|
但使用 @layer
,假设我想优先考虑 .button
类选择器。我可以塑造特异性顺序应该如何:
|
|
由于 @layer
的工作方式,.button
会赢,因为组件层是最高优先级,即使 #button
具有更高的特异性。因此,在 CSS 甚至检查通常的特异性规则之前,层顺序首先被尊重。
你只需要尊重 W3C 的人们,因为现在可以有意识地用简单的类覆盖 ID 选择器,甚至不使用 !important
。迷人。
层叠层的细微差别
当我们谈论 CSS 层叠层时,以下是一些值得注意的事情:
- 特异性仍然是游戏的一部分。
!important
在@layer
中的行为与预期不同(它们反向工作!)。@layers
不是选择器特定的,而是样式属性特定的。
|
|
@layer
很容易被滥用。我确信有一个开发者有超过 20+ 层声明,已经变成了一个怪物。
比较所有三种方法
现在,对于 TL;DR 的人们,这里是 BEM、工具类和 CSS 层叠层的并排比较。
特性 | BEM | 工具类 | 层叠层 |
---|---|---|---|
核心思想 | 命名空间组件 | 单一目的类 | 控制层叠顺序 |
特异性控制 | 低且平坦 | 完全避免 | 由于层至上而绝对控制 |
代码可读性 | 由于命名而结构清晰 | 如果不熟悉类名则不清晰 | 如果遵循层结构则清晰 |
HTML 冗长度 | 中等类名(可能变长) | 许多小类快速增加 | 无直接影响,仅保留在 CSS 中 |
CSS 组织 | 按组件 | 按属性 | 按优先级顺序 |
学习曲线 | 需要理解约定 | 需要知道工具名称 | 易于上手,但需要深入理解 CSS |
工具依赖 | 纯 CSS | 通常依赖第三方,如 Tailwind | 原生 CSS |
重构易度 | 高 | 中 | 低 |
最佳用例 | 设计系统 | 快速构建 | 需要覆盖的遗留代码或第三方代码 |
浏览器支持 | 全部 | 全部 | 全部(除 IE 外) |
在这三者中,每个都有其甜蜜点:
-
BEM 最适合:
- 有一个需要一致性的清晰设计系统,
- 有一个对 CSS 有不同哲学的团队(BEM 可以是中间立场),
- 样式不太可能在组件之间泄漏。
-
工具类最适合:
- 你需要快速构建,如原型或 MVP,
- 使用基于组件的 JavaScript 框架,如 React。
-
层叠层最有效:
- 处理需要完全特异性控制的遗留代码库,
- 你需要集成第三方库或来自不同来源的样式,
- 处理大型、复杂的应用程序或具有长期维护的项目。
如果我必须选择或排名它们,我会选择工具类与层叠层,而不是使用 BEM。但那只是我!
它们相交的地方(如何协同工作)
在这三者中,层叠层应被视为协调者,因为它可以与其他两种策略一起工作。@layer
是 CSS 层叠架构的基本原则,不像 BEM 和工具类,它们是控制层叠行为的方法论。
|
|
另一方面,使用 BEM 与工具类只会最终冲突:
|
|
我摊牌了:我是一个工具优先的开发者。大多数工具类框架在幕后使用 @layer
(例如,Tailwind)。所以,那两个已经在袋子里了。
但是,我讨厌 BEM 吗?一点也不!我用了很多,如果必要,我仍然会用。我只是发现命名事物是一项 exhausting 的练习。
也就是说,我们都不同,你可能对什么感觉最好有相反的想法。这真的不重要,这就是这个 web 开发空间的美丽之处。多条路线可以通向同一个目的地。
结论
所以,当谈到比较 BEM、工具类和 CSS 层叠层时,对于控制层叠中的特异性,是否有真正的“赢家”方法?
首先,CSS 层叠层可以说是我们多年来获得的最强大的 CSS 功能。它们不应与 BEM 或工具类混淆,后者是策略而不是 CSS 功能集的一部分。
这就是为什么我喜欢将 BEM 与层叠层结合或工具类与层叠层结合的想法。无论哪种方式,想法是保持特异性低,并利用层叠层为这些样式设置优先级。