一次大型项目重构后的样式方案选择:CSS-in-JS vs 传统CSS实战经验分享
开篇:从一个项目重构说起

去年年底,我参与了一个大型中后台系统的前端重构工作。这个项目原本是用jQuery + 原生HTML/CSS实现的,随着业务功能不断叠加,样式代码逐渐变得难以维护:命名冲突、层级混乱、样式污染问题频出,开发过程中常常改一个按钮颜色就要花上大半天时间排查。
项目决定迁移到 React 技术栈,其中关于样式方案的选择引发了团队内的激烈讨论。大家争论的核心问题是:继续沿用传统的 CSS/SASS 写法,还是尝试更现代的 CSS-in-JS 方案?
这篇文章就基于那次重构中的实际体验,聊聊我在不同阶段使用 CSS 和 CSS-in-JS 的感受、踩过的坑、做出选择时的关键判断点,以及最终方案落地后的真实效果反馈。希望能给正在做技术选型或面临类似纠结的你一些启发和参考。
一、问题描述:为什么我们要重新考虑样式管理方式?

在重构前,我们面对的问题可以用三个词概括:“难以维护”、“容易冲突”、“缺乏模块化”。
举个具体的例子:
我们有一个通用的 Button 组件,类名是 .btn,它被多个页面复用。某天产品说:“点击按钮后要显示 loading 状态。”于是一个开发兄弟加了新的样式:
.btn.is-loading {
background: #999;
}
看起来没毛病。结果第二天 QA 测试发现,在首页某个组件里所有用了 .is-loading 的元素都变了灰——因为别的地方也有类似的类名!而这类问题在整个项目中层出不穷。
更让人头疼的是,项目初期没有统一的命名规范,不同人写的 class 名五花八门:.button, .btn, .baseBtn, .submitBtn, .loadingBtn……
这些问题总结下来,其实暴露了传统 CSS 的几个明显短板:
- 全局作用域导致的样式冲突
- 难以追踪依赖关系
- 缺乏逻辑表达能力(不能写变量、函数等)
- 样式和组件结构分离,难以直观看到对应关系
所以我们迫切需要一种能解决这些问题的新方案。
二、解决方案对比:CSS 还是 CSS-in-JS?

当时团队内部形成了两种主流声音:
主张继续用 CSS 的理由:
- 学习成本低,老成员熟悉;
- 可以利用 SCSS 写变量、mixins 等提升可维护性;
- 构建体积更小(CSS 文件比 JS 生成的 style 更小);
- 对 SEO 友好,服务端渲染无闪屏。
主张尝试 CSS-in-JS 的理由:
- 模块化强,天然防冲突;
- 支持条件样式、动态主题等高级特性;
- 样式与组件绑定更紧密,利于封装;
- 支持类型推断(如 with TypeScript);
- 现代框架生态中越来越多采用 CSS-in-JS(如 Material UI、Ant Design)。
经过多轮讨论和技术调研,我们决定尝试 CSS-in-JS,并选择了 emotion 作为主要工具库(原因后面会讲)。
三、技术选型细节:为何是 Emotion 而非 Styled Components?

在众多 CSS-in-JS 库中,最主流的两个是 Styled Components 和 Emotion。我们最终选择 Emotion 的理由主要有以下几点:
- 编译性能更好:在大型项目中,emotion 的构建速度明显优于 styled-components,尤其在 SSR 场景下。
- 支持多种用法:既可以通过
styled()创建组件,也可以直接内联对象传给css()方法,灵活性更高。 - TypeScript 支持更友好:配合 Babel 插件可以自动推断组件 props 类型。
- 社区活跃度高且稳定:我们调研时 emotion 已经在多个大型开源项目中验证过稳定性。
比如下面是一个典型的 emotion 使用方式:
/** @jsx jsx */
import { css, jsx } from '@emotion/react'
const Button = ({ primary }) => (
<button
css={css`
background-color: ${primary ? '#007bff' : '#ccc'};
border: none;
color: white;
padding: 10px 20px;
&:hover {
opacity: 0.9;
}
`}
>
Click Me
</button>
)
通过这种方式,我们可以做到:

- 每个组件的样式独立作用域;
- 动态传入 props 控制样式;
- 直接使用 JavaScript 表达式控制样式值;
- 避免全局 class 名冲突。
四、实战踩坑记录与解决方案
虽然 CSS-in-JS 提供了更模块化的写法,但在实际项目中也遇到了一些预料之外的问题。这里分享几个印象深刻的案例。
🕳️ 坑1:样式重复 & 性能问题
刚开始大量使用 inline css 属性时,我们在一个循环组件中写了类似这样的代码:
function ListItem({ item, active }) {
return (
<div
css={{
color: active ? 'red' : 'black',
fontSize: '16px'
}}
>
{item.name}
</div>
)
}
这样写在视觉上没问题,但会导致每次渲染都生成新的 class name,造成重复插入 <style> 标签,影响渲染性能。
✅ 解决方案:
将样式抽离到外部定义,避免在 render 中动态创建:
const listItemStyle = (active) => css`
color: ${active ? 'red' : 'black'};
font-size: 16px;
`
function ListItem({ item, active }) {
return <div css={listItemStyle(active)}>{item.name}</div>
}
这样即使多次调用 listItemStyle,emotion 也会缓存相同的样式规则,避免重复插入。
🕳️ 坑2:服务器端渲染(SSR)样式闪烁
我们项目中有部分页面需要 SSR,初次尝试时发现加载时出现明显的“闪白”现象:样式还没加载进来,内容先以默认样式渲染了。
✅ 解决方案:
确保客户端和服务端渲染出的 class 名一致,并正确注入关键 CSS。
使用 @emotion/server 提供的 extractCriticalToChunks 和 renderStylesToString:
// Node.js 端处理
import { extractCriticalToChunks, renderStylesToString } from '@emotion/server'
const html = ReactDOMServer.renderToString(app)
const chunks = extractCriticalToChunks(html)
const emotionHTML = renderStylesToString(chunks)
// 最终拼接进 HTML 模板
res.send(`
<html>
<head>
<style data-emotion="css">${emotionHTML}</style>
</head>
...
</html>
`)
🕳️ 坑3:调试和浏览器兼容性问题
开发过程中我们也遇到了一些调试相关的困扰:
- Chrome DevTools 显示的是 emotion 自动生成的 class 名(如
_abcde_1),不易定位原始样式; - IE11 上偶尔出现样式不生效的问题;
- 生产环境 build 后某些样式丢失。
✅ 解决方案:
Chrome 调试增强: 安装插件 Emotion Devtools,可以展示原始 CSS 内容。
IE11 兼容性处理: 在 emotion 配置中启用 auto-prefixer,使用
postcss-preset-env来转换新语法。防止样式丢失: 确保 Webpack 或 Rollup 正确配置了 emotion 插件,否则生产环境可能会因 tree-shaking 导致样式未正确引入。
五、实施效果:样式管理真的变轻松了吗?

迁移完成之后,我们团队对这次方案变化做了回顾总结,以下是几个显著的正向改变:
| 评估维度 | 传统 CSS | CSS-in-JS(emotion) |
|---|---|---|
| 样式冲突频率 | 高 | 几乎为零 |
| 样式可读性 | 分散 | 与组件紧密结合 |
| 动态逻辑支持 | 需额外 JS 处理 | 原生支持 |
| 调试效率 | 一般 | 初期略慢,后期更快 |
| 构建性能 | 快 | 中等(需合理拆分样式) |
| 维护成本 | 随项目膨胀上升 | 保持稳定 |
特别是在组件封装方面,我们可以把完整的样式+逻辑打包成一个独立组件,对外只暴露有限 props,极大提升了复用性和可维护性。
举个真实场景:以前写一个表格列过滤控件,我们需要写一堆 class 名来控制隐藏/展开状态;现在可以直接通过 props 控制样式逻辑,代码清晰很多。
六、我的建议:根据项目情况做选择
虽然在这次重构中 CSS-in-JS 带来了不少好处,但我并不推荐所有人一股脑全换成 CSS-in-JS。是否采用这种方案,还是要结合项目的具体情况。
✅ 推荐使用 CSS-in-JS 的几种情况:
- 项目是中大型 SPA,组件化程度高;
- 需要动态主题切换、样式按状态变化的场景多;
- 团队有一定 React 经验;
- 使用 TypeScript,希望有更强的类型保障;
- 强调组件封装和样式隔离。
❌ 不建议使用的几种情况:
- 是纯静态页或内容站,不需要太多交互;
- 项目技术栈锁定 jQuery,不打算重构;
- 团队对 ES6/React 不熟悉;
- 性能极其敏感(例如移动端优先、加载速度为核心指标);
- 有历史包袱,短期内无法全面替换。
如果你不确定该选哪种方案,我的建议是:从小范围开始试点,逐步评估。
比如可以在新开发的模块中用 CSS-in-JS,旧模块维持原状,逐步过渡。这样可以最小化风险,也方便比较两者的优劣。
七、结尾:没有银弹,只有权衡的艺术
写到这里,突然想起之前一位前辈说过的一句话:
“技术选择就像买鞋子,再贵再好的品牌,穿不上自己的脚也没用。”
无论是传统 CSS 还是 CSS-in-JS,都不是万能钥匙。真正重要的不是用什么技术,而是清楚知道每种方案的边界和适用场景。
在这次重构中,CSS-in-JS 让我们更好地解决了项目初期遗留的样式管理难题,提高了协作效率和代码质量。但我也很清楚,这条路并非一帆风顺,前期投入的学习成本和技术适配也不能忽视。
希望你能从我的经历中获得一些启发,找到适合你们团队的那双“鞋”。
如果你也在用 CSS-in-JS,或者正在考虑是否采用,欢迎留言交流。我们一起探讨更多实践心得,少走弯路!
如果你觉得这篇文章对你有帮助,不妨点个赞、转发一下,让更多开发者看到~你的反馈是我持续输出的动力!

评论 0