Tailwind CSS:顺风CSS

前言

最近在折腾 React 项目的时候,深深被 Tailwind CSS 迷住了。以前写 CSS 总觉得乱糟糟的,类名起得头疼,样式复用率低,改个颜色还得全局搜索。现在用上 Tailwind,感觉整个人都轻松了,直接在 HTML(或者 JSX)里堆类名,就能快速搭出好看的界面。这篇文章就分享一下我的学习过程,从传统 CSS 的痛点,到原子 CSS 的概念,再到 Tailwind 的实战应用。希望能帮到同样在纠结样式问题的朋友。

传统 CSS 的痛点:为什么我觉得它不够用了

拿我最早写的代码来说,看这个简单的按钮例子:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <style>
        /* 坏例子:每个按钮一个独立的类,样式几乎不复用 */
        .primary-btn {
            padding: 8px 16px;
            background: blue;
            color: white;
            border-radius: 6px;
        }
        .default-btn {
            padding: 8px 16px;
            background: #ccc;
            color: #000;
            border-radius: 6px;
        }
    </style>
</head>
<body>
    <button class="primary-btn">提交</button>
    <button class="default-btn">默认</button>
</body>
</html>

看起来简单吧?但如果项目里有几十种按钮变体呢?每个都写一个类,CSS 文件很快就膨胀了。而且这些类带着强烈的"业务属性",比如 primary、default,复用性很差。换个项目,这些类名基本废了。

后来我学到"面向对象 CSS"(OOCSS)的思路:把样式拆分成可复用的基类,然后组合起来。

改成这样:

CSS 复制代码
.btn {
    padding: 8px 16px;
    border-radius: 6px;
    cursor: pointer;
}
.btn-primary {
    background: blue;
    color: white;
}
.btn-default {
    background: #ccc;
    color: #000;
}

HTML 里:

HTML 复制代码
<button class="btn btn-primary">提交</button>
<button class="btn btn-default">默认</button>

这就好多了!基类 .btn 处理通用样式,变体类处理差异。通过组合,复用率高了很多。这其实就是原子 CSS 的雏形:把样式拆成一个个小的、不可分的"原子"单元。

原子 CSS:样式世界的乐高积木

原子 CSS(Atomic CSS)就是把 CSS 规则拆成极小的单一职责类,比如:

  • p-4:padding: 1rem;
  • bg-blue-600:background-color: #2563eb;
  • text-white:color: white;
  • rounded-md:border-radius: 0.375rem;

这些类就像乐高积木,单个没什么用,但组合起来能搭出任何形状。好处显而易见:

  • 高度复用:一个类可以在无数地方用,不用担心命名冲突。
  • 不用跳文件:样式直接写在 HTML 里,开发时上下文切换少,效率高。
  • 一致性强:所有间距、颜色都来自统一的设计系统,不会乱七八糟。
  • 生产包小:Tailwind 会自动移除未使用的类,最终 CSS 文件超级精简(往往只有几十 KB)。

Tailwind CSS 就是原子 CSS 的代表作。它不提供现成的组件(像 Bootstrap 的卡片、导航),而是给你一堆 utility 类,让你自己拼。很多人刚看觉得"类名好长好乱",但用习惯了就回不去了------因为它让你专注于布局和设计,而不是纠结类名。

相比传统 CSS,Tailwind 的优势:

  • 开发速度快:不用写自定义 CSS,90% 的样式直接用 utility 类搞定。
  • 维护容易:改样式直接改类名,不用找 CSS 文件。
  • 响应式友好:内置 mobile-first 设计(后面详说)。
  • 自定义强:通过配置文件,能轻松调整颜色、间距等设计 token。

当然,不是完美:HTML 看起来类名多("class soup"),初学曲线陡。但多练几次,就爱上了。

在 React + Vite 项目中上手 Tailwind CSS

2025 年了,Tailwind 已经到 v4 了,性能更强,内置 Vite 支持超级方便。我的 React 项目是用 Vite 创建的,安装步骤超级简单。

  1. 创建项目:
Bash 复制代码
npm create vite@latest my-tailwind-project -- --template react
cd my-tailwind-project
npm install
  1. 安装 Tailwind(v4 方式):
Bash 复制代码
npm install tailwindcss @tailwindcss/vite
  1. 配置 vite.config.js:
JavaScript 复制代码
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
})
  1. 导入:

在你的 CSS 文件中添加一个@import导入 Tailwind CSS 的语句。

CSS 复制代码
@import "tailwindcss";

就这么简单!不用 postcss.config.js 了,v4 原生支持 Vite。

启动 npm run dev,你就可以在 JSX 里用 Tailwind 类了。

看我第一个组件:

jsx 复制代码
function App() {
  return (
    <>
      <h1 className="text-4xl font-bold text-center my-8">Hello Tailwind!</h1>
      <button className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
        提交
      </button>
      <button className="px-4 py-2 bg-gray-300 text-black rounded-md hover:bg-gray-400 ml-4">
        默认
      </button>
    </>
  )
}

直接在 className 里堆类:padding 用 px/py,背景 bg-xxx,文字 text-xxx,hover 状态直接 hover: 前缀。太爽了!

对应样式:

我还做了个小卡片组件:

jsx 复制代码
const ArticleCard = () => {
  return (
    <div className="p-4 bg-white rounded-xl shadow hover:shadow-lg transition-shadow">
      <h2 className="text-lg font-bold">Tailwind CSS</h2>
      <p className="text-gray-500 mt-2">用 utility class 快速构建 UI</p>
    </div>
  );
};

hover 的时候阴影变大,transition 平滑过渡,全是 Tailwind 内置的。

TailWindCss的痛点

1. HTML/JSX 里类名超级长,看起来乱糟糟的(Class Soup 现象)

这是最多人吐槽的点。一个组件的 className 动不动就一长串

html 复制代码
className="flex flex-col md:flex-row gap-4 p-6 bg-white rounded-lg shadow hover:shadow-xl transition-all"。

看起来像"类名汤",阅读性差,尤其是新手或别人接手代码时,得花时间解析这些类到底干了啥。相比传统 CSS 一个语义化的类名(如 .card),Tailwind 把样式全暴露在 markup 里,违反了"关注点分离"的原则------HTML 本该管结构,CSS 管样式,现在全混一起了。

解决办法:提取组件、用 @apply 自定义类,或者工具如 tailwind-merge 处理条件类。但一开始还是挺别扭的。

效果展示:

2. 学习曲线陡峭,得记一大堆类名

Tailwind 的类名不是随便起的,得熟悉它的命名规则:padding 用 p-,margin 用 m-,响应式前缀 md: lg:,hover: focus: 之类的变体。

刚上手时,总得翻文档或靠记忆。很多人说"用一周就上手了",但对 CSS 不熟的新人来说,可能得一个月才能流畅。

还有,复杂样式(如嵌套选择器、父子状态)用纯 Tailwind 写起来麻烦,得靠 group- 或插件。

解决方法:VS Code 的 IntelliSense 插件提示

效果展示:

3. 维护和重构时麻烦,尤其是大项目

想全局改个间距(如所有 padding 从 4 改成 6)?得搜索替换一堆类名。传统 CSS 改一个变量或类就行。

条件样式(比如根据 props 动态类)需要额外工具如 clsx 或 tailwind-merge,否则容易冲突。

调试时,浏览器 DevTools 里一堆原子类,虽然能点开看,但不如语义类直观。

大项目里,如果不严格规范,容易样式重复或不一致。

Tailwind 的响应式设计:Mobile First 是王道

Tailwind 默认是 mobile-first:先写移动端样式,再用前缀(如 md: lg:)覆盖大屏。

比如我做的布局:

jsx 复制代码
export default function App2() {
  return (
    <div className="flex flex-col gap-4 p-4 md:flex-row">
      <main className="bg-blue-100 p-4 rounded-lg md:w-2/3">主内容区</main>
      <aside className="bg-green-100 p-4 rounded-lg md:w-1/3">侧边栏</aside>
    </div>
  );
}
  • 默认(手机):flex-col,垂直栈。
  • md 及以上:flex-row,横排;main 占 2/3,aside 占 1/3。

为什么 mobile-first 好?因为手机流量占一半以上,先保证小屏体验好,再逐步增强大屏。传统 desktop-first 容易忽略手机。

Tailwind 断点默认:

  • sm: 640px
  • md: 768px
  • lg: 1024px
  • xl: 1280px
  • 2xl: 1536px

想只在某个范围生效?组合 max- 前缀,比如 md:max-lg: 只在中屏。

进阶小技巧和最佳实践

用 Tailwind 久了,有些心得:

  1. 类名顺序有讲究:我喜欢按"同心圆"顺序:布局 → 盒模型 → 背景 → 文字 → 其他。这样读起来顺眼。
  2. 重复类太多?提取组件:别在每个按钮都写一长串,封装成 < Button variant="primary" >。
  3. 用 @apply 自定义类:如果某些组合常用,在 CSS 里:
CSS 复制代码
.btn-primary {
  @apply px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700;
}
  1. 暗黑模式超简单:加 class="dark" 到 html,utility 用 dark: 前缀。
  2. 性能优化:Tailwind 自动 purge 未用类,生产包很小。
  3. 和大模型结合:Tailwind 类名语义强,用 AI 生成 UI 特别准。描述"一个蓝色大按钮,hover 变深",直接吐类名。

React 中的 Fragment

React 组件为什么只能返回一个根元素(没有并列的多个根元素)?

你在学 React 时,肯定碰到过这个经典报错:

错误信息大概是:"JSX expressions must have one parent element" 或 "Adjacent JSX elements must be wrapped in an enclosing tag"。

简单说,就是 React 函数组件的 return 里,只能返回一个单一的根元素,不能并列多个顶级元素

错的例子:

jsx 复制代码
function App() {
  return (
    <h1>标题</h1>
    <p>段落</p>  // 报错!两个并列
  );
}

对的例子(传统方式):

jsx 复制代码
function App() {
  return (
    <div>  // 用 div 包裹成一个根
      <h1>标题</h1>
      <p>段落</p>
    </div>
  );
}

现在很多人用 Fragment:

jsx 复制代码
function App() {
  return (
    <>  // 空标签,就是 Fragment
      <h1>标题</h1>
      <p>段落</p>
    </>
  );
}

那为什么 React 要强制这个规则呢?不能直接支持并列多个根吗?

核心原因:JSX 本质上是 React.createElement 的语法糖

React 组件其实就是一个函数,这个函数必须返回 一个值(单个 React Element)。

JSX 看起来像 HTML,但它会被 Babel 编译成 JavaScript 的 React.createElement 调用。

比如这个 JSX:

jsx 复制代码
<div>
  <h1>标题</h1>
  <p>段落</p>
</div>

编译后大致是:

JavaScript 复制代码
React.createElement("div", null,
  React.createElement("h1", null, "标题"),
  React.createElement("p", null, "段落")
);

createElement 返回的是 一个单一的 React Element 对象,它可以有多个 children(子元素数组),但本身必须是单个对象。

如果你写并列两个:

jsx 复制代码
<h1>标题</h1>
<p>段落</p>

编译后相当于:

JavaScript 复制代码
React.createElement("h1", null, "标题");
React.createElement("p", null, "段落");  // 两个独立的调用

这就像函数里有两条 return 语句:

JavaScript 复制代码
function bad() {
  return 1;
  return 2;  // 第二个永远执行不到,语法无效
}

JavaScript 函数只能返回一个值,所以 React 组件也只能返回一个 React Element。

另一个重要原因:React 的虚拟 DOM 和 Reconciliation(调和)算法需要树状结构

React 的核心是虚拟 DOM:一个 JavaScript 对象树。

每个组件渲染后,必须对应虚拟 DOM 树上的 一个节点(根节点),这个节点可以有任意多个子节点,但组件本身只能代表一个节点。

如果允许并列多个根,React 就不知道这个组件在虚拟 DOM 树里该怎么挂载、怎么 diff(比较新旧虚拟 DOM)、怎么更新。

React 的 diff 算法依赖严格的树结构:父子关系清晰,便于高效比较和更新。

多根的话,树就乱了,算法复杂度会爆炸,性能变差。

历史背景:早期只能用 div 包裹,后来引入 Fragment

React 早期(v16 之前),大家只能用无意义的 < div> 包裹。

问题:

  • 多一层 DOM 节点,影响 CSS 布局(比如 flex、grid)
  • 在表格 里返回多个 ,包 div 会导致无效 HTML
  • DOM 树更深,略微影响性能

React v16(2017 年)引入 Fragment,专门解决这个痛点。

Fragment 就是一个"透明包裹",不渲染成真实 DOM 节点,只在虚拟 DOM 里占位。

写法:

jsx 复制代码
import { Fragment } from 'react';
// 或直接用短语法(推荐)
return (
  <Fragment>
    <h1>标题</h1>
    <p>段落</p>
  </Fragment>
);

// 最常用:
return (
  <>
    <h1>标题</h1>
    <p>段落</p>
  </>
);

注意:短语法 <></> 不支持 key 属性,如果在 map 里需要 key,就用 < Fragment key=...>。

总结

  • 技术原因:JSX → React.createElement 只能返回单个元素,函数只能 return 一个值。
  • 算法原因:虚拟 DOM 需要严格树结构,便于高效 diff 和更新。
  • 解决方案:用 Fragment(<>...</>)包裹,既满足规则,又不污染真实 DOM。

这个规则虽然一开始让人觉得麻烦,但它保证了 React 的高效和可预测性。用习惯 Fragment 后,你会觉得超级自然。

为什么叫TailWind

"Tailwind" 在英文里是"顺风"的意思,听起来挺诗意的,跟 CSS 框架有啥关系?

其实,名字的来源超级随意,来自它的创始人 Adam Wathan 本人的一次脑洞大开。

Adam 在一次访谈中亲口说过(大概 2022 年的播客):

他当时在 brainstorm 项目名字,从 "tail" 这个词开始联想------先想到 "tail tail"(重复),然后 "white tail"(白尾?可能是白尾鹿),突然蹦出 Tailwind

他说:"That's a cool name. 那名字挺酷的,而且有点道理,因为它能帮你做事更快(do stuff faster)。"

"Tailwind" 在英语里确实有"顺风"的含义,比如飞机起飞时如果有 tailwind,就能飞得更快、更顺畅。Adam 觉得这个框架能让开发者写样式更快、更高效,就像顺风助力一样,所以名字就这么定了。

他说这是他想到的第一个名字,就觉得特别合适,直接敲定了。

Tailwind CSS 最早是 Adam 在 2017 年做一个侧边项目时,顺手搞出来的一个内部工具(用 Less 写的 utility 类)。后来直播开发时,观众老问"这 CSS 框架叫啥",他才决定开源。名字就是这么随便取的,没有什么深奥的典故,就是觉得"酷 + 贴合加速开发的理念"。

有趣的是,Tailwind 的 slogan 也是 "Rapidly build modern websites without ever leaving your HTML"(快速构建现代网站,不用离开 HTML),完美呼应了"顺风加速"的感觉。

所以,Tailwind 这个名字本质上就是:

  • 随意脑暴而来:从 "tail" 联想到的词。
  • 寓意开发加速:像顺风一样,让你写 UI 更快、更顺手。

写在最后:为什么我推荐你试试 Tailwind

从传统 CSS 到 Tailwind,我的感觉是:从"手艺人"变成了"建筑师"。以前抠每一个像素写规则,现在直接用现成积木搭,专注在产品逻辑和用户体验上。项目迭代快了,代码干净了,心情也好了。

如果你还在用传统 CSS 纠结,不妨在新项目里试试 Tailwind + React。刚开始可能不适应类名多,但坚持一周,你会爱上这种自由。

相关推荐
栀秋6663 小时前
防抖 vs 节流:从百度搜索到京东电商,看前端性能优化的“节奏哲学”
前端·javascript
王小菲3 小时前
《网页布局速通:8 大主流方案 + 实战案例》-pink老师现代网页布局总结
css·面试·html
有意义3 小时前
深入防抖与节流:从闭包原理到性能优化实战
前端·javascript·面试
2503_928411564 小时前
12.26 小程序问题和解决
前端·javascript·微信小程序·小程序
over6974 小时前
防抖与节流:前端性能优化的“双子星”,让你的网页丝滑如德芙!
前端·javascript·面试
red润4 小时前
手把手封装Iframe父子单向双向通讯功能
前端·javascript·vue.js
gustt4 小时前
JavaScript 闭包实战:手写防抖与节流函数,优化高频事件性能
前端·javascript·面试
止水编程 water_proof4 小时前
JQuery 基础
前端·javascript·jquery
小p4 小时前
react学习15:基于 React Router 实现 keepalive
react.js