抽取组件
这是系列的最后一篇。前面 10 篇我们都在原地写 class——一个元素的样式全堆在它自己的 class 里。但有时候同一组样式会重复出现(比如 5 个按钮长得一样),复制粘贴明显是反模式。这时候 Tailwind 提供了 @apply 指令,让你把一串类"封装"成一个自定义类。
1. 何时该抽取?
Adam Wathan 给的判断标准非常清晰:
- 同一串 class 出现 3 次以上:复制成本超过抽取成本。
- 语义明确:它代表一个明确的 UI 概念(按钮、卡片、徽章)。
- 未来会复用:相信后面还会出现第 4、第 5 次。
反过来,如果只出现 1~2 次,或语义模糊,不要过早抽象。Tailwind 的哲学就是"重复胜于错误的抽象"——错误的抽象比复制粘贴更难维护。
2. 问题场景
看一个典型的重复案例:
<!-- 场景:同一个按钮在 5 个地方出现,class 完全相同 -->
<button class="px-4 py-2 rounded-md bg-blue-500 hover:bg-blue-600
text-white text-sm font-medium transition">
提交
</button>
<button class="px-4 py-2 rounded-md bg-blue-500 hover:bg-blue-600
text-white text-sm font-medium transition">
保存
</button>
<button class="px-4 py-2 rounded-md bg-blue-500 hover:bg-blue-600
text-white text-sm font-medium transition">
登录
</button>
<!-- 问题:改主色时要改 5 处,容易漏 -->3. @apply 基础用法
用 @apply 把这串类封装成 .btn-primary:
/* 在 CSS 文件里用 @apply 把一组类封装成自定义类 */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply px-4 py-2 rounded-md bg-blue-500 hover:bg-blue-600
text-white text-sm font-medium transition;
}
}
/* 之后 HTML 里就能用了 */
/* <button class="btn-primary">提交</button> */之后 HTML 里就能像普通 CSS 类一样使用。改主色时只改一处,所有按钮自动更新。
4. @layer 三层结构
@apply 通常配合 @layer components 使用。Tailwind 的 CSS 分三层,顺序有讲究:
/* Tailwind 的三层(layer),顺序有讲究:
base => 全局重置/基础样式(如 preflight)
components => 组件级类(用 @apply 抽象的)
utilities => 工具类(原子类,优先级最高)
后面的层会覆盖前面,所以 utilities 永远赢 */
@layer base {
/* 全局基础样式,如重置 */
body { @apply bg-gray-50 text-gray-900; }
h1 { @apply text-3xl font-bold; }
}
@layer components {
/* 抽象出的组件类 */
.btn { @apply px-4 py-2 rounded-md font-medium transition; }
.btn-primary { @apply btn bg-blue-500 hover:bg-blue-600 text-white; }
.btn-danger { @apply btn bg-red-500 hover:bg-red-600 text-white; }
.card { @apply bg-white rounded-lg shadow-md p-6; }
}
@layer utilities {
/* 自定义工具类(罕见,一般用 plugin) */
.scrollbar-hide { scrollbar-width: none; }
.scrollbar-hide::-webkit-scrollbar { display: none; }
}关键规律:
base:全局重置、基础样式。改 body 默认字体、链接颜色等放这里。components:用@apply抽象出的组件类(btn、card)。utilities:工具类,优先级最高。所以 HTML 里写的p-4永远能覆盖组件类里的@apply p-2。
5. 组合:基础类 + 变体
实际项目里,通常会定义一个基础类 + 多个变体类,组合使用:
/* @apply 可以引用其他自定义类,实现"组合" */
@layer components {
.btn {
@apply inline-flex items-center px-4 py-2 rounded-md
text-sm font-medium transition cursor-pointer;
}
.btn-primary { @apply btn bg-blue-500 hover:bg-blue-600 text-white; }
.btn-ghost { @apply btn bg-transparent hover:bg-gray-100 text-gray-700; }
.btn-danger { @apply btn bg-red-500 hover:bg-red-600 text-white; }
}
/* HTML 用法 */
/* <button class="btn-primary">提交</button> */
/* <button class="btn-ghost">取消</button> */
/* <button class="btn-danger">删除</button> */
/* 还可以混用:btn-primary + 额外类 */
/* <button class="btn-primary w-full">登录</button> */这种 pattern 是 Bootstrap/Bulma 的核心思想,在 Tailwind 里用 @apply 也能实现。但记住——用得要克制。
6. HTML 端的变体 pattern
另一种常见写法是 HTML 端"基础类 + 修饰类"分离:
<!-- HTML 端的"变体" pattern:基础类 + 修饰类 -->
<button class="btn btn-primary">提交</button>
<button class="btn btn-danger">删除</button>
<button class="btn btn-primary btn-lg">大号提交</button>
@layer components {
.btn { @apply px-4 py-2 rounded-md font-medium transition; }
.btn-primary { @apply bg-blue-500 hover:bg-blue-600 text-white; }
.btn-danger { @apply bg-red-500 hover:bg-red-600 text-white; }
.btn-lg { @apply px-6 py-3 text-base; }
.btn-sm { @apply px-3 py-1 text-xs; }
}7. 在组件化框架里,优先抽组件!
如果你用 React/Vue/Astro/Next.js,抽取组件比抽取 CSS 类更好:
// 在 React/Vue/Astro 等组件化框架里,
// 抽取"组件"比抽取"CSS 类"更好
// React 示例:
function Button({ variant = 'primary', children, ...props }) {
const variants = {
primary: 'bg-blue-500 hover:bg-blue-600 text-white',
danger: 'bg-red-500 hover:bg-red-600 text-white',
ghost: 'bg-transparent hover:bg-gray-100 text-gray-700',
};
return (
<button
className={`px-4 py-2 rounded-md text-sm font-medium transition
${variants[variant]}`}
{...props}
>
{children}
</button>
);
}
// 用法:<Button variant="primary">提交</Button>
// 优势:类型安全 + IDE 跳转 + props 文档,远超 CSS 类组件化的优势:
- 类型安全:TypeScript 能校验 props。
- IDE 支持:跳转定义、自动补全。
- 可文档化:props 有注释,使用方式一目了然。
- 逻辑封装:不仅是样式,行为(点击、loading 态)也一起封装。
所以现代项目里,@apply 已经很少使用。优先抽 React/Vue 组件,@apply 只用于"组件化无法解决"的场景(纯静态多页站点、CMS 输出的 HTML 无法改 class 等)。
8. @apply 的常见陷阱
- 用得过多:又会回到"传统 CSS 一堆类"的老路,失去 utility-first 的灵活性。
- 提前抽象:以为"将来会重复"就抽,结果只用了 1 次。等真有需求再抽。
- 用
@apply应用group-hover::旧版 Tailwind 不支持,新版可以但容易出错。复杂状态前缀还是直接写在 HTML 里。 - 忽略
@layer:不写@layer components,自定义类的优先级会错乱,可能被 utilities 错误覆盖。 - 覆盖默认
btn:别起太通用的类名(card、btn),容易和第三方库冲突。加前缀(my-btn)或用 BEM 风格。
9. 何时坚持不抽取
下面这些情况,即使看起来"重复",也建议不抽取:
- 页面专属布局:只在一个页面用的 hero/section 布局,即使有 20 个类,也直接写在 HTML 里。抽出来反而增加跳转成本。
- 实验性 UI:还在迭代的样式,先在 HTML 里改方便,等稳定再抽。
- 一次性使用的复杂样式:不是"重复",只是"复杂"。
10. 重构:从传统 CSS 迁移到 Tailwind
对老项目迁移 Tailwind,@apply 是过渡期的最佳工具:
- 第一步:在 HTML 加 Tailwind,先不删旧 CSS,新旧共存。
- 第二步:把高频 class(如
.btn)用@apply在新 CSS 里重写,HTML 不动。 - 第三步:逐步把 HTML 里旧类替换成 utility 类,删除旧 CSS。
- 第四步:稳定后,把可以抽组件的迁移到 React/Vue 组件,
@apply进一步减少。
11. 系列总结
恭喜你完成了 Tailwind CSS 的 11 篇系统学习!回顾一下整个系列的脉络:
- 理念层:简介 → 环境安装 → utility-first,理解"为什么"。
- 核心类:间距尺寸 → 布局 → 颜色背景 → 字体排版,掌握 80% 高频类。
- 进阶:响应式 → 状态前缀,让页面适应屏幕和交互。
- 工程化:自定义配置 → 抽取组件,把 Tailwind 改造成团队的设计系统。
12. 下一步学什么?
- 实战项目:用 Tailwind 写一个完整的个人主页、博客主题、后台 dashboard。
- 读官方文档:tailwindcss.com/docs 把所有类按功能分组,带可视化预览。本系列是骨架,官方文档是血肉。
- 看 Tailwind UI:tailwindui.com 的免费示例是学习"如何写出有设计感的代码"的最佳范本。
- 学习 Headless UI:@headlessui/react 或 vue,提供无样式的可访问组件(下拉、对话框),配合 Tailwind 完美。
- 跟进 Tailwind v4:2024 年发布的新版本,引擎重写、性能大幅提升,语法有微调。
Tailwind 背后代表的"约束系统 + 原子化"思想,是近年来 CSS 领域最具影响力的创新,值得每个前端深入学习。祝你写出优雅、高效、可维护的样式代码!
← 返回 Tailwind CSS 教程目录