Utility-first 原子化思想
上一篇我们装好了环境。在开始系统学具体类之前,先花一篇讲清楚 Tailwind 的核心理念——"utility-first"。理解了这一层,后面看到一长串 class 就不会害怕,反而能体会它的精妙。
1. 传统 CSS 的写法:关注点分离
我们从入门就被教导"HTML 管结构、CSS 管样式、JS 管行为",这是著名的关注点分离(Separation of Concerns)原则。所以传统写法长这样:
<!-- 传统 CSS:HTML 管结构,CSS 管样式,关注点分离 -->
<button class="btn btn-primary">提交</button>
<style>
.btn {
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
}
</style>这种写法听起来很美,但实际项目一大就暴露问题:
- 起名难:为了给按钮起个类名,要纠结
btn-primary还是button-blue,然后 BEM 命名规范上场,类名越拉越长。 - 样式难删:你想删掉
.btn-primary,但要先全局搜索谁在用它,生怕删错影响别的页面。 - 覆盖痛苦:购物车页想让按钮变红色,只能写
.cart .btn-primary { background: red },一层层覆盖。 - 跳转频繁:改一个按钮,要先打开 HTML 找结构,再切到 CSS 找规则,改完两个文件。
2. Tailwind 的写法:样式就地
Tailwind 完全颠覆这个思路。它说:"关注点分离"只是手段,不是目的。真正的目的是"代码可维护"。把所有样式信息集中在元素上,反而更易维护:
<!-- Tailwind:把样式"就地"写在 class 里 -->
<button class="px-4 py-2 rounded-md text-sm cursor-pointer
bg-blue-500 text-white hover:bg-blue-600">
提交
</button>每个 utility 类只做一件事,组合起来描述样式:
<!-- 每个 utility 类只做一件事(单一职责):
px-4 => padding-left/right: 16px
py-2 => padding-top/bottom: 8px
rounded-md => border-radius: 6px
text-sm => font-size: 14px
bg-blue-500 => background: #3b82f6
hover:bg-blue-600 => 鼠标悬停时 background: #2563eb
像搭乐高一样组合 -->3. 为什么这样更好?
Adam Wathan 在那篇著名的文章里逐步论证了这种"看似反模式"的写法实际上是更优的工程实践。核心理由有四:
- 不用起类名:这是被严重低估的优势。心智负担骤降,专注力放回业务本身。
- 改一处不影响别处:class 是"就地"的,改这个按钮的样式,绝不会波及别的元素。删除安全。
- 不需要跳转文件:所有信息都在这一个
class字符串里,IDE 智能补全就在光标处。 - 约束系统:你只能用 Tailwind 提供的类(基于设计系统),不能随手写
padding: 13px这种魔法值——设计天然一致。
4. 一个真实场景
来看一个"传统 CSS 越改越乱"的典型场景,对比 Tailwind 的处理方式:
<!-- 场景:同一个按钮,要在购物车页让背景变红色 -->
<!-- 传统做法:
1. CSS 里加一个 .btn-danger { background: red; }
2. 但购物车页想用红色,首页还想要蓝色
3. 于是又有了 .btn-primary--cart 这种 BEM 变种
4. 几个月后,类名爆炸 -->
<!-- Tailwind 做法:直接改 class,影响范围仅限当前元素 -->
<button class="px-4 py-2 rounded-md bg-red-500 text-white">
删除
</button>
<!-- 其他位置的按钮完全不受影响 -->Tailwind 的关键优势在于:局部修改就是局部的。不会因为加了一个红色按钮,而导致全局 CSS 文件膨胀、命名空间被污染。
5. "看起来很丑"是真的吗?
对 Tailwind 最常见的批评是"class 字符串太长、看着像垃圾代码"。这是一种视觉上的不适,不是工程上的缺陷。实际写起来你会发现:
- 90% 的元素 class 不会超过 10 个,熟了以后盲打速度极快。
- VS Code IntelliSense 插件能自动补全,输入
p就弹列表。 - 同一组件复用时,在 React/Vue 里封装成组件后,class 只在组件内部出现一次。
- 真的过长时(20+ 个类),可以折行书写、按用途分组。
6. 什么时候用 @apply 抽组件?
Tailwind 提供了 @apply 指令,允许你在 CSS 里把一组 utility 类"塞进"一个自定义类名里:
这是 Tailwind 留的"逃生口"——当一串 class 真的出现 3 次以上、语义明确(比如"主按钮")时,可以用 @apply 抽象成一个类。但 Adam Wathan 强烈建议克制使用:
- 过早抽象会让你又回到传统 CSS 的老路,失去 utility-first 的灵活性。
- 在组件化框架(React/Vue)里,抽取组件比抽取 CSS 类更好。
- 只在"传统多页面静态站点、组件化无法解决"时才考虑
@apply。
具体怎么用、什么时候用,后面会有专门一篇讲。
7. 不适合的场景
诚实地说,Tailwind 不是银弹,有些场景不太合适:
- 需要严格隔离样式的第三方组件库:Tailwind 的
preflight(基础 reset)会重置全局样式,可能影响嵌入的第三方组件。 - 对设计师交付的 CSS 高度依赖的项目:设计师给的 CSS 一行不动就要用,Tailwind 反而要重写一遍。
- 团队抗拒变化:有人死守"语义化类名才正确"的教条,引入前先达成共识。
小结
Utility-first 不是"把 CSS 写进 HTML"那么简单,它是一套用约束系统替代自由发挥的工程哲学。接受了这套理念,后面的具体类学起来会非常顺——因为它们都是高度规律化、可预测的。下一篇我们从最常用的"间距与尺寸"开始,正式进入类库的学习。
← 上一篇 环境安装与配置
下一篇 间距与尺寸 →