今天尝试 面向对象 CSS。封装基类,利用多态处理业务差异,通过组合形成新类。
原子化 CSS,也就是今天的主角 —— Tailwind CSS。本篇文章基于react实现。
为什么选择了 Tailwind CSS?
Tailwind CSS 的核心理念是将 CSS 规则拆分成最小的“原子”。
就像tailwindcss 官网主页说的:
主打 “不离开 HTML 即可快速构建现代网站”,适合快速原型开发与自定义设计。
- 极致的复用:它提供了海量的基类,我们可以像搭积木一样自由组合。
- 拥抱 AI 时代:最近在研究 LLM(大语言模型)辅助编程,我发现 Tailwind 特别友好。因为它的类名语义化极强,Prompt 描述布局时,AI 生成的代码非常准确。
一、tailwindcss 配置
这里可以去 tailwindcss 官网查看,我这里就简单过一遍。
1. 安装 tailwindcss 和vite 插件
npm install tailwindcss @tailwindcss/vite
2. vite.config.js 配置
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})
3. 导入 tailwindcss
@import "tailwindcss";
4. 使用
后面我们就可以在App.jsx中直接使用tailwindcss了。
二、实战:从布局到组件
在我的项目中,Tailwind 让响应式布局和组件开发变得异常简单。
1. 移动优先的响应式布局
看在这个 App.jsx 的例子,我需要实现一个经典的“主内容 + 侧边栏”布局。
export default function App() {
// Mobile First 策略
// 默认是 flex-col (垂直排列),适配移动端
// md:flex-row (中等屏幕以上水平排列),适配 PC 端
return (
<>
<div className="flex flex-col md:flex-row gap-4">
<main className="bg-blue-500 p-4 md:w-2/3">
主内容
</main>
<aside className="bg-gray-200 p-4 md:w-1/3">
侧边栏
</aside>
</div>
</>
)
}
注意 md: 前缀。Tailwind 默认采用 Mobile First(移动优先)策略。我们首先定义移动端的样式(flex-col),也兼容了 PC 端的基础样式。然后通过 md:flex-row 告诉浏览器:“当屏幕宽度大于 md 断点时,切换成水平排列”。这比手写 @media 查询要优雅太多了。
2. 组件封装与交互状态
再来看看 App2.jsx,我们可以把一堆 utility classes 封装成一个 React 组件,同时轻松处理 hover 等状态。
const ArticleCard = () => {
// JSX + tailwindcss(UI 的一部分) = UI
return (
<div className="p-4 bg-white rounded-xl shadow hover:shadow-lg transition">
<h2 className="text-lg font-bold">Tailwindcss</h2>
<p className="text-gray-500 mt-2">
用utlity class 快速构建UI
</p>
</div>
)
}
export default function App() {
return (
<>
<h1>111</h1>
<h2>222</h2>
<button className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-red-700">提交</button>
<button className="px-4 py-2 bg-blue-300 text-black rounded-md hover:bg-gray-400">默认</button>
<ArticleCard />
</>
)
}
这里 hover:shadow-lg 和 transition 配合,一行代码就实现了卡片的悬停浮起效果。我们不需要为一个简单的交互去写单独的 CSS 类。
3. 不满意,那就改
tailwind.config.js是 Tailwind CSS 的配置文件,用于自定义和扩展 Tailwind 的默认行为
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{html,js,jsx,ts,tsx}", // 告诉 Tailwind 去哪些文件里扫描用到的 class
],
theme: {
extend: {
colors: {
brand: '#38bdf8', // 自定义颜色:text-brand, bg-brand
},
spacing: {
'128': '32rem', // 自定义间距:p-128, m-128
},
fontFamily: {
sans: ['Inter', 'sans-serif'], // 修改默认无衬线字体
}
},
},
plugins: [
require('@tailwindcss/forms'), // 引入官方插件
],
}
⚠ 即使没有创建 tailwind.config.js 文件,Tailwind CSS 依然可以正常工作( 有 默认配置)。
三、延伸:从“样式碎片”到“文档碎片”
当我们用 Tailwind 把 CSS 拆解成原子级的“碎片”来优化样式管理时,我不禁联想到在 DOM 操作层面,我们其实也有类似的概念 —— Fragment(片段)。
在 App2.jsx 中,你可能注意到了这个写法:
export default function App() {
return (
// Fragment: 临时的容器,不会渲染到 DOM 中
<>
<h1>111</h1>
<h2>222</h2>
<ArticleCard />
</>
)
}
深入 DocumentFragment
在 React 中,我们使用 <></>(Fragment)来包裹组件,目的是为了维持树状结构便于遍历,也就是解决单一根节点限制的问题,同时不渲染额外的多余 div 节点。
但在原生 JavaScript 的底层,这个概念其实对应的是 DocumentFragment(文档碎片节点)。它不仅仅是为了“不渲染多余标签”,更是为了性能优化。
来看一段原生 JS 的对比:
const container = document.querySelector('.container');
const p1 = document.createElement('p');
const p2 = document.createElement('p');
// ❌ 普通做法:多次操作 DOM
// container.appendChild(p1); // 触发一次重排/重绘
// container.appendChild(p2); // 又触发一次
// ✅ 性能优化做法:使用 Fragment
const fragment = document.createDocumentFragment();
fragment.appendChild(p1); // 先挂载到内存中的碎片上
fragment.appendChild(p2);
// 一次性挂载,只触发一次浏览器的渲染流程
container.appendChild(fragment);
DocumentFragment 提供了一个轻量级的文档对象。可以把它当做一个隐形的“暂存区”。所有的 DOM 操作都在这个暂存区(内存)里完成,最后我们只需要把这个暂存区一次性贴到页面上。
这和 Tailwind 的理念在某种程度上是殊途同归的:
- Tailwind:通过原子类组合,避免了像传统 CSS 那样生成大量重复、冗杂的样式表,让 HTML 结构更纯粹。
- Fragment:通过内存批处理,避免了频繁的 Real DOM 交互,让页面渲染更高效。