CSS-in-JS vs 传统CSS:现代样式方案选择指南
从“样式战争”到“各司其职”:我在前端样式方案上的选择与思考

我第一次听到“CSS-in-JS”的时候,内心是抗拒的。
彼时我正负责一个中型 React 项目的重构。项目初期我们还是用的传统 CSS + SCSS 预处理器,配合 BEM 命名规范来组织代码结构。但随着功能迭代、模块越来越多、团队新人不断加入,我们开始遭遇一系列令人头疼的问题:
- 类名冲突导致样式互相污染
- 全局样式覆盖难以追踪
- 业务逻辑与样式强耦合后维护成本陡增
- 复杂组件的状态变体(hover、focus、disabled)管理混乱
那段时间每天都要在开发者工具里翻来覆去查元素、看 computed styles,和产品经理拉扯各种“为什么按钮会变成红色?”这类问题。更糟的是,有些样式 Bug 居然是因为第三方组件库和自定义样式产生了意外交互——这简直是灾难现场。
于是我开始认真研究起当时社区热议的 CSS-in-JS 方案,想看看能不能找到更好的解法。
项目背景:一次复杂的后台系统重构
我们的项目是一个面向企业用户的 SaaS 平台后台管理系统,包含用户管理、权限配置、数据可视化等多个核心模块,技术栈主要是 React + TypeScript + Ant Design。
原本的架构采用传统方式:
components/
Button/
index.jsx
style.scss
containers/
Dashboard/
index.jsx
style.scss
globalStyles/
_reset.scss
_variables.scss
每个组件都有自己独立的样式文件,在开发初期一切井然有序。然而当团队人数从原来的 3 人增加到 7 人后,问题接踵而至:
- 类名冲突 ——
Dashboard和UserList同时用了.title - 样式全局污染 —— 某个同事在
globalStyles加了一个通用的.table-row样式,结果影响了所有表格组件 - 样式复用困难 —— 想把一个组件的样式拆出来给另一个使用?得手动 copy-paste、重命名、调整变量,效率极低
- 调试复杂度上升 —— 开发者工具里显示的类名是
.style__title__2356,但源码却分布在多个文件中

转折点:尝试引入 styled-components
经过技术选型后,我们决定试点引入 styled-components,并选择其中几个关键组件进行重构。改造前后的差异非常直观,比如:
传统写法:
// components/Button/index.jsx
import './Button.scss';
const Button = ({ children, onClick }) => (
<button className="btn" onClick={onClick}>
{children}
</button>
);
/* components/Button/Button.scss */
.btn {
background-color: #007BFF;
color: white;
&:hover {
background-color: #0069D9;
}
}
改造为 styled-components 写法:
// components/Button/index.jsx
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: #007BFF;
color: white;
&:hover {
background-color: #0069D9;
}
`;
const Button = ({ children, onClick }) => (
<StyledButton onClick={onClick}>{children}</StyledButton>
);
这个改动看似不大,但在实际开发过程中带来了几个显著的变化:
- 组件样式内聚性提升:样式定义就在 JS 文件内部,不需要来回切换文件查找样式来源
- 避免命名冲突:通过动态生成唯一的 class 名解决类名重复问题
- 支持 props 动态注入样式:可以轻松实现根据状态变换样式的场景
举个例子,我们需要一个按钮根据不同类型展示不同颜色:
const StyledButton = styled.button`
background-color: ${props => props.primary ? '#007BFF' : 'gray'};
color: white;
`;
这样直接通过组件 props 控制样式,完全无需再写多个类名组合判断。
实际挑战与性能考量
尽管 CSS-in-JS 看起来很美好,但我们在实践中也遇到不少问题,尤其是在首次引入阶段。
1. 首屏渲染慢 & FOUC 问题
最明显的就是页面加载瞬间偶尔会出现“样式未应用”的内容闪烁(Flash of Unstyled Content)。排查下来发现原因有两个:
- styled-components 使用服务端渲染(SSR)时没有正确提取静态样式
- 客户端初次挂载时动态注入样式需要时间
解决方案:
- 引入
@emotion/styled替代部分 styled-components,因为它对 SSR 友好程度更高 - 在 Next.js 项目中启用
getServerSideProps提取静态样式 - 增加骨架屏或 loading 占位符优化用户体验
2. 可维护性 vs 性能的权衡
虽然 CSS-in-JS 的封装很好,但随着组件增多,我们发现:
- 组件内部样式越来越复杂,代码臃肿
- 调试时浏览器开发者工具显示的 class 名是一串 hash,不利于排查
最终做法:
- 对于基础组件库(如 Button、Input),继续使用 styled-components
- 对于业务组件或容器级组件,回归传统 CSS Modules 写法 + SCSS 变量管理
- 使用 postcss-prefixwrap 为全局样式自动加上前缀防止污染
3. 构建产物体积增长
使用 CSS-in-JS 后,打包体积明显变大。尤其是项目中很多组件其实共享了一组主题色、字体大小等变量,但我们把它们都写进了各自的 styled 组件中,而不是统一引用设计变量。
后来我们做了两个优化:
- 抽离出
theme.ts统一管理设计 token - 所有样式尽量基于 theme 中的变量编写
// theme.ts
export const colors = {
primary: '#007BFF',
danger: '#DC3545',
};
import styled from 'styled-components';
import { colors } from '../../theme';
const StyledButton = styled.button`
background-color: ${colors.primary};
`;
最终效果与收益总结
整个过渡过程持续了大约两个月,从最初的全盘拒绝,到最后我们逐步摸索出了一个混合使用的最佳实践路径。最终的效果体现在以下几个方面:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 类名冲突问题 | 高频出现 | 几乎绝迹 |
| 样式复用难度 | 高(依赖 SCSS mixin) | 中等(组件抽象 + 主题变量) |
| 开发体验 | 需频繁切换文件 | 更流畅的一体化编码 |
| 构建性能 | 中等 | 小幅下降 |
| 首屏加载体验 | 偶尔闪现 | 优化后稳定 |
特别值得一说的是团队协作体验的提升。之前经常出现“你改了一个 SCSS 文件导致五个组件出问题”,而现在每个人只负责自己的组件样式,几乎不再出现这种牵一发动全身的 Bug。
我的经验建议:别站队,看场景
现在回头看,我觉得当初争论“CSS-in-JS 好不好”这件事本身就很片面。真正重要的是理解每种方案背后的设计理念,并根据项目实际情况做出取舍。
以下是我的几点实战建议:
✅ 适合使用 CSS-in-JS 的情况:
- 组件化程度高、追求样式封闭性的项目(如设计系统、UI 库)
- 需要根据组件状态动态生成样式的场景
- 团队规模大,多人协作容易产生类名冲突
推荐方案:
✅ 传统 CSS/SCSS 依旧适用的场景:
- 页面级布局和全局样式
- 追求极致性能的小型项目
- 需要兼容老旧浏览器(如 IE11)
推荐搭配:
- CSS Modules 或 SCSS Modules
- postcss + autoprefixer 自动加厂商前缀
- purgeCSS 清理未使用 CSS
🚫 不建议的场景:
- 所有样式都一股脑塞进 styled 组件中 → 导致 JS 包体积暴涨
- 忽视样式复用 → 失去了设计系统的优势
- 不做性能监控 → 没有意识到 CSS-in-JS 的副作用
工具推荐与调试小技巧
在使用过程中,我发现一些工具和技巧大大提升了效率:
- React Developer Tools:可以查看某个元素对应的 styled 组件名称(虽然类名看不懂)
- Chrome DevTools 的 Computed Tab:实时看样式的继承和优先级
- Emotion 插件支持:VSCode 插件能高亮 emotion 的模板字符串
- Lighthouse 性能检测:检查是否存在过多嵌套或冗余样式规则
另外,如果你想了解项目中的样式是否真的按需加载,可以试试这个命令:
npx bundlewatch
它会分析你的构建产物,给出各个包的大小分布,帮助你识别潜在问题。
结语:样式方案没有银弹
在这场“CSS-in-JS vs 传统 CSS”的旅程中,我最大的收获不是找到了所谓的“终极答案”,而是明白了技术选型的本质其实是:找到最适合当前团队和项目阶段的最佳平衡点。
现在的我们并没有抛弃传统 CSS,也没有全盘拥抱 CSS-in-JS,而是根据场景灵活使用:
- 基础组件库用 styled-components / emotion
- 业务页面用 CSS Modules + SCSS 变量管理
- 全局样式放在单独入口统一注册
如果你正在面临类似的选型难题,不妨先问问自己这几个问题:
- 我们团队的协作模式是什么样的?
- 是否存在类名冲突或全局污染的历史问题?
- 是否需要根据状态动态改变样式?
- 项目是否需要 SEO 或 SSR?
- 用户体验和首屏性能是我们最关心的吗?
带着这些问题去看文档、跑 Demo,再结合自己的真实需求下判断,远比盲目跟随趋势要靠谱得多。
最后送大家一句话,也是我的一句心得:
“样式之争不在于谁更先进,而在于谁能更快让你安心写完组件,然后回家吃饭。”
愿你在每一行代码中都能感受到创造的乐趣 😊

评论 0