抽取组件

这是系列的最后一篇。前面 10 篇我们都在原地写 class——一个元素的样式全堆在它自己的 class 里。但有时候同一组样式会重复出现(比如 5 个按钮长得一样),复制粘贴明显是反模式。这时候 Tailwind 提供了 @apply 指令,让你把一串类"封装"成一个自定义类。

1. 何时该抽取?

Adam Wathan 给的判断标准非常清晰:

反过来,如果只出现 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; }
}

关键规律:

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 类

组件化的优势:

所以现代项目里,@apply 已经很少使用。优先抽 React/Vue 组件,@apply 只用于"组件化无法解决"的场景(纯静态多页站点、CMS 输出的 HTML 无法改 class 等)。

8. @apply 的常见陷阱

9. 何时坚持不抽取

下面这些情况,即使看起来"重复",也建议不抽取:

10. 重构:从传统 CSS 迁移到 Tailwind

对老项目迁移 Tailwind,@apply过渡期的最佳工具:

11. 系列总结

恭喜你完成了 Tailwind CSS 的 11 篇系统学习!回顾一下整个系列的脉络:

12. 下一步学什么?

Tailwind 背后代表的"约束系统 + 原子化"思想,是近年来 CSS 领域最具影响力的创新,值得每个前端深入学习。祝你写出优雅、高效、可维护的样式代码!

← 返回 Tailwind CSS 教程目录

✈️💬