Kodokon kodokon.com

CSS 架构:BEM、utility-first、文件组织

比较 BEM 与 utility-first 方案、它们在团队中真实的取舍,并用层叠层来组织你的文件。

11 分钟 · 3 题

在 Kodokon 中打开本课

CSS 不是在一个组件的规模上崩溃,而是在一个项目的规模上崩溃:为了"战胜"某条既有规则而不断抬高特异性、不敢删除那些你不知道其影响范围的代码、样式从一个屏幕泄漏到另一个屏幕。CSS 架构首先是一套管理特异性与作用域的策略。两大主流学派——BEM 与 utility-first——用相反的路径回答同一个问题。

BEM(Block、Element、Modifier)为每一个带样式的节点命名:.card(一个独立的块)、.card__title(属于该块的一个元素)、.card--featured(该块的一个变体)。这套约定保证了扁平的特异性——处处都是单个类,从不使用后代选择器——因此不会有排序冲突,而且名称本身就记录了归属。代价是:不断地发明名称,以及真实存在的值重复(同一个 margin-top: 16px 在二十个块里被重写)。

CSS
.card {
  padding: 16px;
  border-radius: 8px;
}

.card__title {
  font-size: 1.25rem;
}

.card__title--muted {
  color: #6b7280;
}

.card--featured {
  border: 2px solid var(--brand);
}
BEM:扁平特异性、显式归属

utility-first 方案(Tailwind 是其主流化身)颠倒了逻辑:不再有名称,只有在 HTML 中组合起来的单一职责原子类。收益是可衡量的:零命名成本、样式与标记就近共处(删掉组件就删掉了它的样式),以及一个体积几乎恒定的最终 CSS 包,因为这些工具类是共享的。取舍也存在:冗长的 HTML、被转移到标记里的重复(这需要一个组件层——React、Vue、局部模板——来保持 DRY),以及学习词汇表的曲线。

HTML
<article class="flex flex-col gap-4 rounded-lg
  border border-gray-200 p-4 shadow-sm">
  <h2 class="text-lg font-semibold">Invoice</h2>
  <p class="text-sm text-gray-600">
    Paid on March 12
  </p>
</article>
utility-first:样式活在标记里

这些方案并不互斥:许多成熟的团队会把它们结合起来——用工具类做布局和间距,用组件类(BEM 或 CSS Modules)处理复杂的模式和状态。至于文件组织,ITCSS 原则依旧是参照标准:从通用到具体地排序(重置、基础元素、组件、工具类)。原生的 @layer 规则把这套约定变成引擎级别的保证:层与层之间的优先级独立于它们所含选择器的特异性

CSS
@layer reset, base, components, utilities;

@layer base {
  h2 { font-size: 1.5rem; }
}

@layer components {
  #hero .btn { padding: 12px 24px; }
}

@layer utilities {
  .p-0 { padding: 0; }
}
层叠层:顺序胜过特异性

知识检测

确认你已牢记本课的重点内容。

  1. .card__title--muted 中,--muted 这一段表示什么?
    • 一个 CSS 自定义属性
    • 一个修饰符:card__title 元素的一个变体
    • card__title 的一个子元素
    • 一个由 JavaScript 管理的状态
  2. @layer 对层叠的主要贡献是什么?
    • 它提高了该层选择器的特异性
    • 它使层的顺序优先于选择器的特异性
    • 它像 Shadow DOM 那样隔离一个组件的样式
    • 它并行加载各层以加速渲染
  3. 采用 utility-first 方案时,你接受的是什么取舍?
    • 一个随页面数量成比例增长的 CSS 文件
    • 难以覆盖的高特异性
    • 冗长的 HTML,其去重依赖于一个组件层