• Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • React 通关秘籍

    • 1.关于本小册
    • 2.一网打尽组件常用Hook
    • 3.Hook的闭包陷阱的成因和解决方案
    • 4.React组件如何写TypeScript类型
    • 5.React组件如何调试
    • 6.受控模式VS非受控模式
    • 7.组件实战:迷你Calendar
    • 8.组件实战:Calendar日历组件(上)
    • 9.组件实战:Calendar日历组件(下)
    • 10.快速掌握Storybook
    • 11.React组件如何写单测
    • 12.深入理解Suspense和ErrorBoundary
    • 13.组件实战:Icon图标组件
    • 14.组件实战:Space间距组件
    • 15.React.Children和它的两种替代方案
    • 16.三个简单组件的封装
    • 17.浏览器的5种Observer
    • 18.组件实战:Watermark防删除水印组件
    • 19.手写react-lazyload
    • 20.图解网页的各种距离
    • 21.自定义hook练习
    • 22.自定义hook练习(二)
    • 23.用react-spring做弹簧动画
    • 24.react-spring结合use-gesture手势库实现交互动画
    • 25.用react-transition-group和react-spring做过渡动画
    • 26.快速掌握Tailwind:最流行的原子化CSS框架
    • 27.用CSSModules避免样式冲突
    • 28.CSSInJS:快速掌握styled-components
    • 29.react-spring实现滑入滑出的转场动画
    • 30.组件实战:Message全局提示组件
    • 31.组件实战:Popover气泡卡片组件
    • 32.项目里如何快速定位组件源码
    • 33.一次超爽的React调试体验
    • 34.组件实战:ColorPicker颜色选择器(一)
    • 35.组件实战:ColorPicker颜色选择器(二)
    • 36.组件实战:onBoarding漫游式引导组件
    • 37.组件实战:Upload拖拽上传
    • 38.组件实战:Form表单组件
    • 39.React组件库都是怎么构建的
    • 40.组件库实战:构建esm和cjs产物,发布到npm
    • 41.组件库实战:构建umd产物,通过unpkg访问
    • 42.数据不可变:immutable和immer
    • 43.基于ReactRouter实现keepalive
    • 44.Historyapi和ReactRouter实现原理
    • 45.ReactContext的实现原理和在antd里的应用
    • 46.ReactContext的性能缺点和解决方案
    • 47.手写一个Zustand
    • 48.原子化状态管理库Jotai
    • 49.用react-intl实现国际化
    • 50.国际化资源包如何通过Excel和GoogleSheet分享给产品经理
    • 51.基于react-dnd实现拖拽排序
    • 52.react-dnd实战:拖拽版TodoList
    • 53.ReactPlayground项目实战:需求分析、实现原理
    • 54.ReactPlayground项目实战:布局、代码编辑器
    • 55.ReactPlayground项目实战:多文件切换
    • 56.ReactPlayground项目实战:babel编译、iframe预览
    • 57.ReactPlayground项目实战:文件增删改
    • 58.ReactPlayground项目实战:错误显示、主题切换
    • 59.ReactPlayground项目实战:链接分享、代码下载
    • 60.ReactPlayground项目实战:WebWorker性能优化
    • 61.ReactPlayground项目实战:总结
    • 62.手写MiniReact:思路分析
    • 63.手写MiniReact:代码实现
    • 64.手写MiniReact:和真实React源码的对比
    • 65.React18的并发机制是怎么实现的
    • 66.Ref的实现原理
    • 67.低代码编辑器:核心数据结构、全局store
    • 68.低代码编辑器:拖拽组件到画布、拖拽编辑json
    • 69.低代码编辑器:画布区hover展示高亮框
    • 70.低代码编辑器:画布区click展示编辑框
    • 71.低代码编辑器:组件属性、样式编辑
    • 72.低代码编辑器:预览、大纲
    • 73.低代码编辑器:事件绑定
    • 74.低代码编辑器:动作弹窗
    • 75.低代码编辑器:自定义JS
    • 76.低代码编辑器:组件联动
    • 77.低代码编辑器:拖拽优化、Table组件
    • 78.低代码编辑器:Form组件、store持久化
    • 79.低代码编辑器:项目总结
    • 80.快速掌握ReactFlow画流程图
    • 81.ReactFlow振荡器调音:项目介绍
    • 82.ReactFlow振荡器调音:流程图绘制
    • 83.ReactFlow振荡器调音:合成声音
    • 84.AudioContext实现在线钢琴
    • 85.React服务端渲染:从SSR到hydrate
    • 86.小册总结

Tailwind 是流行的原子化 css 框架。

有多流行呢?

它现在有 76k star 了,npm 包的周下载量也很高:

那什么是原子化 css?

我们平时写 css 是这样的:

<div class="aaa"></div>
.aaa {
    font-size: 16px;
    border: 1px solid #000;
    padding: 4px;
}

在 html 里指定 class,然后在 css 里定义这个 class 的样式。

也就是 class 里包含多个样式:

而原子化 css 是这样的写法:

<div class="text-base p-1 border border-black border-solid"></div>
.text-base {
    font-size: 16px;
}
.p-1 {
    padding: 4px;
}
.border {
    border-width: 1px;
}
.border-black {
    border-color: black;
}
.border-solid {
    border-style: solid;
}

定义一些细粒度的 class,叫做原子 class,然后在 html 里直接引入这些原子化的 class。

这个原子化 css 的概念还是很好理解的,但它到底有啥好处呢? 它解决了什么问题?

口说无凭,我们试下 tailwind 就知道了,它就是一个提供了很多原子 class 的 css 框架。

我们通过 crerate-react-app 创建一个 react 项目:

npx create-react-app tailwind-test

然后进入 tailwind-test 目录,执行

npm install -D tailwindcss

npx tailwindcss init

安装 tailwindcss 依赖,创建 tailwindcss 配置文件。

tailwind 实际上是一个 postcss 插件,因为 cra 内部已经做了 postcss 集成 tailwind 插件的配置,这一步就不用做了:

然后在入口 css 里加上这三行代码:

这三行分别是引入 tailwind 的基础样式、组件样式、工具样式的。

之后就可以在组件里用 tailwind 提供的 class 了:

import "./App.css";

function App() {
    return (
        <div className="text-base p-1 border border-black border-solid">
            guang
        </div>
    );
}

export default App;

我们执行把开发服务跑起来:

npm run start

可以看到,它正确的加上了样式:

用到的这些原子 class 就是 tailwind 提供的:

这里的 p-1 是 padding:0.25rem,你也可以在配置文件里修改它的值:

在 tailwind.config.js 的 theme.extend 修改 p-1 的值,设置为 30px。

刷新页面,就可以看到 p-1 的样式变了:

.text-base 是 font-size、line-height 两个样式,这种通过数组配置:

也就是说所有 tailwind 提供的所有内置原子 class 都可以配置。

但这些都是全局的更改,有的时候你想临时设置一些值,可以用 [] 语法。

比如 text-[14px],它就会生成 font-size:14px 的样式:

比如 aspect-[4/3],就是这样的样式:

我们平时经常指定 hover 时的样式,在 tailwind 里怎么指定呢?

很简单,这样写:

生成的就是带状态的 class:

此外,写响应式的页面的时候,我们要指定什么宽度的时候用什么样式,这个用 tailwind 怎么写呢?

也是一样的写法:

生成的是这样的代码:

这个断点位置自然也是可以配置的:

可以看到 md 断点对应的宽度变了:

光这些就很方便了。

之前要这么写:

<div class="aaa"></div>
.aaa {
    background: red;
    font-size: 16px;
}

.aaa:hover {
    font-size: 30px;
}

@media (min-width: 768px) {
    .aaa {
        background: blue;
    }
}

现在只需要这样:

<div class="text-[14px] bg-red-500 hover:text-[30px] md:bg-blue-500"></div>

省去了很多样板代码,还省掉了 class 的命名。

并且这些 class 都可以通过配置来统一修改。

感受到原子化 css 的好处了么?

tailwind 文档提到了 3 个好处:

不用起 class 名字,这点简直太爽了,我就经常被起 class 名字折磨。

css 不会一直增长,因为如果你用之前的写法可能是这样的:

多个 class 里都包含了类似的样式,但你需要写多次,而如果用了原子 class,就只需要定义一次就好了。

css 没有模块作用域,所以可能你在这里加了一个样式,结果别的地方样式错乱了。

而用原子 class 就没这种问题,因为样式是只是作用在某个 html 标签的。

我觉得光这三点好处就能够说服我用它了,特别是不用起 class 名字这点。

当然,社区也有一些反对的声音,我们来看看他们是怎么说的:

一堆 class,可读性、可维护性太差了

真的么?

这种把 css 写在 html 里的方式应该是更高效才对。

想想为啥 vue 要创造个单文件组件的语法,把 js、css、template 放在一个文件里写,不就是为了紧凑么?

之前你要在 css、js 文件里反复跳来跳去的,查找某个 class 的样式是啥,现在不用这么跳了,直接在 html 里写原子样式,它不香么?

而且 tailwindcss 就前面提到的那么几个语法,没啥学习成本,很容易看懂才对。

但是还要每次去查文档哪些 class 对应什么样式呀

这个可以用 tailwind css 提供的 vscode 插件来解决:

安装这个 Tailwind CSS IntelliSense 之后的体验是这样的:

有智能提示,可以查看它对应的样式。

不需要记。

这个插件触发提示需要先敲一个空格,这点要注意下:

难以调试

在 chrome devtools 里可以直接看到有啥样式,而且样式之间基本没有交叉,很容易调试:

相反,我倒是觉得之前那种写法容易多个 class 的样式相互覆盖,还要确定优先级和顺序,那个更难调试才对:

类型太长了而且重复多次

这种问题可以用 @layer @apply 指令来扩展:

前面讲过 @tailwind 是引入不同的样式的,而 @layer 就是在某一层样式做修改和扩充,里面可以用 @apply 应用其他样式。

效果是这样的:

内置 class 不能满足我的需求

其实上面那个 @layer 和 @apply 就能扩展内置原子 class。

但如果你想跨项目复用,那可以开发个 tailwind 插件

const plugin = require("tailwindcss/plugin");

module.exports = plugin(function ({ addUtilities }) {
    addUtilities({
        ".guang": {
            background: "blue",
            color: "yellow",
        },
        ".guangguang": {
            "font-size": "70px",
        },
    });
});

在 tailwind.config.js 里引入:

这样就可以用这个新加的原子 class 了:

插件的方式或者 @layer 的方式都可以扩展。

tailwind 的 class 名和我已有的 class 冲突了咋办?

比如我本来有个 border 的 class:

而 tailwind 也有,不就冲突了么?

这个可以通过加 prefix 解决:

不过这样所有的原子 class 都得加 prefix 了:

知道了什么是原子 css 以及 tailwind 的用法之后,我们再来看看它的实现原理。

tailwind 可以单独跑,也可以作为 postcss 插件来跑。这是因为如果单独跑的话,它也会跑起 postcss,然后应用 tailwind 的插件:

所以说,tailwind 本质上就是个 postcss 插件。

postcss 是一个 css 编译器,它是 parse、transform、generate 的流程。

在 astexplorer.net 可以看到 postcss 的 AST:

而 postcss 就是通过 AST 来拿到 @tailwind、@layer、@apply 这些它扩展的指令,分别作相应的处理,也就是对 AST 的增删改查。

那它是怎么扫描到 js、html 中的 className 的呢?

这是因为它有个 extractor (提取器)的东西,用来通过正则匹配文本中的 class,之后添加到 AST 中,最终生成代码。

extractor 的功能看下测试用例就明白了:

所以说,tailwind 就是基于 postcss 的 AST 实现的 css 代码生成工具,并且做了通过 extractor 提取 js、html 中 class 的功能。

tailwind 还有种叫 JIT 的编译方式,这个原理也容易理解,本来是全部引入原子 css 然后过滤掉没有用到的,而 JIT 的话就是根据提取到的 class 来动态引入原子 css,更高效一点。

最后,为啥这个 css 框架叫 tailwind 呢?

因为作者喜欢叫做 kiteboarding 风筝冲浪的运动。

就是这样的,一个风筝,一个冲浪板:

这种运动在顺风 tailwind 和逆风 headwind 下有不同的技巧。而 tailwind 的时候明显更加省力。

所以就给这个 css 框架起名叫 tailwind 了。

确实,我也觉得用这种方式来写 css 更加省力、高效,不用写 class 名字了,代码更简洁了,还不容易样式冲突了。

案例代码上传了小册仓库

总结

tailwind 是一个流行的原子化 css 框架。

传统 css 写法是定义 class,然后在 class 内部写样式,而原子化 css 是预定义一些细粒度 class,通过组合 class 的方式完成样式编写。

tailwind 用起来很简单:

所有预定义的 class 都可以通过配置文件修改值,也可以通过 aaa-[14px] 的方式定义任意值的 class。

所有 class 都可以通过 hover:xxx、md:xxx 的方式来添加某个状态下的样式,响应式的样式,相比传统的写法简洁太多了。

它的优点有很多,我个人最喜欢的就是不用起 class 的名字了,而且避免了同样的样式在多个 class 里定义多次导致代码重复,并且局部作用于某个标签,避免了全局污染。

它可以通过 @layer、@apply 或者插件的方式扩展原子 class,支持 prefix 来避免 class 名字冲突。

tailwind 本质上就是一个 postcss 插件,通过 AST 来分析 css 代码,对 css 做增删改,并且可以通过 extractor 提取 js、html 中的 class,之后基于这些来生成最终的 css 代码。

是否感受到了 tailwind 的简洁高效,易于扩展?就是这些原因让它成为了最流行的原子化 css 框架。

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
25.用react-transition-group和react-spring做过渡动画
Next
27.用CSSModules避免样式冲突