样式方案
React 项目中处理 CSS 的七种主流方案,从最传统的全局 CSS 到现代的原子化方案。
方案对比
| 方案 | 作用域 | 学习曲线 | 包体积 | 动态样式 | 适用场景 |
|---|---|---|---|---|---|
| 全局 CSS | 全局 | 最低 | 0 | ❌ | 极简项目 |
| CSS Modules | 局部 | 低 | 0 | ❌ | 中大型项目 |
| Tailwind CSS | 工具类 | 中 | ~3KB (不包含 purge) | 部分 | 追求开发速度 |
| styled-components | 局部 | 中 | ~13KB | ✅ | 动态样式多 |
| CSS-in-JS (Emotion/Panda) | 局部 | 中 | ~8KB | ✅ | 同上 |
| Inline Style | 局部 | 最低 | 0 | ✅ | 简单动态样式 |
| UI 组件库 | 全局/局部 | 低 | 大 (50KB+) | 部分 | 快速原型/后台 |
方案一:全局 CSS
/* styles.css */
.button {
background: #3b82f6;
color: white;
padding: 8px 16px;
border-radius: 4px;
}// main.jsx — 入口文件引入一次,全局生效
import './styles.css';
<button className="button">Click</button>🚨 陷阱:全局 CSS 命名冲突。BEM 命名规范可以缓解(如
.card__header--large),但无法彻底避免。快速原型阶段可用,正式项目优先选择有作用域的方案。
方案二:CSS Modules(推荐)
Vite 默认支持 .module.css 文件:
/* Button.module.css */
.button {
background: #3b82f6;
color: white;
padding: 8px 16px;
}
.variantOutline {
background: transparent;
border: 2px solid #3b82f6;
color: #3b82f6;
}import styles from './Button.module.css';
function Button({ variant }) {
return (
<button className={`${styles.button} ${variant === 'outline' ? styles.variantOutline : ''}`}>
Submit
</button>
);
}产物中类名自动加哈希:
/* 编译后 */
.Button_button__xk2p4 { background: #3b82f6; }CSS Modules 组合
/* 扩展已有的类 */
.error {
composes: alert from './Alert.module.css';
background: red;
}CSS Modules 最佳实践
💡 最佳实践:
- 一个组件一个
.module.css文件,放在组件旁边- 用
styles.xxx访问类名,让拼写错误在编译时暴露(TypeScript 项目用.module.css.d.ts或 Vite 的类型插件)- 多个类名组合用
clsx辅助库:clsx(styles.button, isActive && styles.active)
方案三:Tailwind CSS
npm install -D tailwindcss @tailwindcss/vitefunction Card({ title, children }) {
return (
<div className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
<h2 className="text-lg font-semibold text-gray-900 mb-4">{title}</h2>
<div className="text-gray-600">{children}</div>
</div>
);
}Tailwind 的优势与争议
| 优势 | 争议 |
|---|---|
| 快速原型和开发 | HTML 看起来很"脏" |
| 天然的作用域(无样式泄漏) | 长类名列表可读性差 |
| 设计系统内建(spacing/color/typography) | 学习工具类名有成本 |
| CSS 体积恒定(purge 后约 3-5KB) | 复杂动画仍需写 CSS |
💡 最佳实践:长列表的类名组合可以用
clsx或tailwind-merge管理,抽取重复样式为组件。Tailwind v4 引入 CSS-first 配置,大幅简化了配置流程。
方案四:CSS-in-JS(styled-components)
npm install styled-componentsimport styled from 'styled-components';
const Button = styled.button`
background: ${props => props.$primary ? '#3b82f6' : '#e5e7eb'};
color: ${props => props.$primary ? 'white' : '#374151'};
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
&:hover {
opacity: 0.9;
}
`;
// 使用
<Button $primary>提交</Button>
<Button>取消</Button>特点
| 优点 | 缺点 |
|---|---|
| 真正的动态样式(基于 props) | 运行时开销(JS 生成 CSS) |
| 无需单独 CSS 文件 | 包体积增加 |
| 自动厂商前缀 | SSR 配置复杂 |
| CSS 语法高亮(编辑器插件) | 难以使用传统 CSS 工具 |
⚡ 性能提示:CSS-in-JS 方案在运行时生成样式注入到 DOM(约 4-8ms 开销/次渲染)。对大多数应用影响不大,但高频渲染场景(动画、大列表)应避免。
方案五:零运行时 CSS(Panda CSS / Vanilla Extract)
新一代 CSS-in-JS 在构建时生成静态 CSS——既有类型安全的 DX,又无运行时开销:
// Panda CSS
import { css } from '../styled-system/css';
function Button() {
return (
<button className={css({
bg: 'blue.500',
color: 'white',
px: '4',
py: '2',
rounded: 'md',
})}>
Submit
</button>
);
}构建时生成纯 CSS 文件,运行时零开销。
方案六:UI 组件库
| 库 | 样式方案 | 特点 |
|---|---|---|
| Ant Design | CSS-in-JS (v5) | 企业级,组件最全面 |
| shadcn/ui | Tailwind + Radix | 复制源码到项目,完全可定制 |
| MUI | Emotion | Material Design,生态好 |
| Chakra UI | Emotion | 开发体验好,组合性强 |
| NextUI | Tailwind | 基于 React Aria,现代风格 |
💡 最佳实践:shadcn/ui 目前是社区首选——它不属于"依赖",而是直接复制源码到你的项目,意味着完全可控制和定制。
方案七:Inline Style
<div style={{ color: 'red', fontSize: 16 }}>
Hello
</div>| 适用 | 不适用 |
|---|---|
| 依赖 JS 数据的动态值 | 伪类(:hover) |
| 少数几个属性 | 伪元素(::before) |
| 位置/大小计算 | 媒体查询 |
| 动画 |
💡 最佳实践:inline style 适合组合其他方案使用。例如用 Tailwind 做静态样式 + inline style 做动态值。
选择决策
项目大小?
├── 极简(几个页面) → 全局 CSS 或 inline
├── 小型 → Tailwind CSS
├── 中型 →
│ ├── 追求开发速度 → Tailwind + shadcn/ui
│ ├── 追求传统分离 → CSS Modules
│ └── 大量动态样式 → styled-components / Panda CSS
└── 大型 → 团队统一选择(Tailwind / CSS Modules / Panda CSS)