CSS-in-JS 与传统 CSS:样式方案的选择,不只是技术之争
在前端开发这条路上,我们经常会遇到各种“路线之争”。而CSS-in-JS 和传统 CSS 的争论,可能是近年来最持久、也最具争议的技术话题之一。
作为一名拥有多年全栈开发经验的团队负责人,我亲身经历过从刀耕火种的手写 CSS 时代,到拥抱模块化工程化的过渡期,再到如今组件化体系下的多种样式解决方案共存。在这个过程中,我也带领团队尝试过多个项目采用不同的样式策略,并为此吃过不少亏,也踩过不少坑。
今天这篇文章,我想通过一个真实的项目案例,来和大家聊聊为什么我们要选择某种方案、当时面对了哪些问题、又做了哪些取舍,以及最后得到的启示。
一、背景介绍:为什么这个讨论重要?

事情要从几年前说起。那时我们在做一个企业级管理后台系统,目标是打造一个高度可复用、可维护、风格统一的 UI 组件库。整个系统由 React 构建,配合 TypeScript 开发,希望能够在多个产品线中共享使用。
最初的设计决策非常简单:使用传统的 CSS 方案,通过 BEM 命名规范结合 PostCSS 插件,实现模块化的样式管理。但随着项目规模的增长,特别是组件数量不断增加,我们开始面临一系列令人头疼的问题:
- 命名冲突问题频发
- 样式难以隔离(尤其是多个组件嵌套时)
- 主题切换困难(需要额外的变量管理和处理逻辑)
- 协作成本高(不同人写的样式结构差异大)
这些问题直接影响到了开发效率和交付质量。于是我们开始重新思考:“有没有一种更现代、更可控的方式来管理样式?”
这就是我们第一次真正意义上认真地去评估——是否要从传统的 CSS 方案转向 CSS-in-JS。
二、问题描述:传统 CSS 在大型项目中的痛点

我们以实际场景为例来说明。
案例一:组件命名冲突导致样式污染
在某个订单模块中,我们定义了一个名为 .order-table 的类名。后来另一个功能模块也开始使用 .order-table 作为其表格容器的类名。虽然语义上是一致的,但由于两个模块独立开发,样式结构和优先级不同,导致页面出现错乱,调试起来异常费劲。
/* A 模块 */
.order-table {
background: #f0f0f0;
}
/* B 模块 */
.order-table td {
padding: 20px;
}
最终效果可能是 A 模块的表格 TD 却有了 B 模块的 padding,这不是设计所期望的。
这类问题的根本原因在于:全局样式没有隔离机制,而且命名规则依赖人为遵守,容易出错。
案例二:主题切换支持不友好
公司想要一套可以动态换肤的主题系统,用于多品牌展示。但在传统 CSS 中,我们需要借助 SASS 变量 + mixin 来实现。每个主题都需要一套完整的 CSS 文件,构建时根据主题注入对应的 class 到 body 上才能生效。
这种方案存在几个问题:
- 编译体积膨胀(每个主题单独打包)
- 动态切换能力差(页面可能需要强制刷新或 JS 手动修改)
- 复杂度高,不易维护
这对我们后续的扩展造成了不小的困扰。
三、我们的探索:引入 CSS-in-JS 方案

基于上述痛点,我们决定尝试使用 CSS-in-JS 技术栈来重构部分组件,并观察其在工程化、样式管理等方面的效果。
我们调研了几种主流方案,包括但不限于:
| 工具 | 特点 | 适用场景 |
|---|---|---|
| styled-components | 支持模板字符串、样式即组件 | React 生态最佳,适合中小型项目 |
| emotion | 支持编译优化、服务端渲染友好 | 大型应用、SSR 场景 |
| JSS | 面向对象配置式 API | 更适合非 JSX 写法项目 |
| Linaria | 构建时生成静态 CSS | 极简主义者首选 |
最终,我们选择了 emotion 作为实验性接入的主要方案,主要原因有两点:
- 性能稳定,在 SSR 场景下表现优异;
- 支持两种写法(styled API 和 object styles),灵活性更高。
四、代码实践:如何优雅地使用 emotion 实现样式封装
接下来分享几个核心代码片段,帮助你理解我们在实践中是如何组织样式的。
示例一:使用 styled API 定义组件样式
import styled from '@emotion/styled';
const Button = styled.button`
background-color: ${props => props.primary ? '#3b82f6' : '#e5e7eb'};
color: ${props => props.primary ? 'white' : 'black'};
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 1rem;
&:hover {
opacity: 0.9;
}
`;
function App() {
return (
<Button primary>提交</Button>
);
}
这种方式的优点是:
- 样式直接绑定组件,作用域封闭;
- 可以接收 props 参数,实现动态样式;
- 易于测试和复用。
示例二:使用 sx Prop(适用于 emotion + MUI)
在整合 Material-UI 的时候,我们还使用了 emotion 提供的 @emotion/react 插件,从而可以像 MUI 的新版本那样使用 sx prop:
<Box sx={{ color: 'primary.main', m: 2 }}>
自定义样式盒
</Box>
这种组合方式大大提升了样式编写效率,尤其是在需要快速调整组件内部元素样式时非常方便。
五、踩过的坑和实战经验
任何新技术的应用都不是一帆风顺的。下面分享我们在引入 CSS-in-JS 过程中踩过的一些坑,以及应对之道。
1. 构建性能下降
初期在未启用 emotion 插件的构建优化时,我们发现整体打包时间明显增加,JS bundle 体积也有一定的上涨。
解决方案:
- 启用了 emotion 的
extractStaticStyles插件,将样式提取为外部 CSS 文件; - 对核心组件进行按需加载,避免一次性全部引入;
// Webpack emotion 插件配置示例
{
test: /\.(tsx?|jsx?)$/,
loader: 'babel-loader',
options: {
plugins: [
['@emotion/babel-plugin', { labelFormat: '[filename]__[local]' }]
]
}
}
2. 调试变得麻烦了
CSS-in-JS 的好处是样式封闭,但也带来一个问题:当你打开 DevTools 查看某个元素时,类名不再是语义清晰的 .btn-primary,而是类似 _abcxyz123 的 hash 字符串,给调试造成一定困扰。
解决方案:
- 使用 emotion 提供的
labelFormat配置项,让类名保留组件路径信息; - 配合 Chrome DevTools 的“Computed Style”面板定位样式来源;
- 使用 React Developer Tools 查看组件层级和 props 传值情况;
['@emotion/babel-plugin', { labelFormat: '[filename]__[local]' }]
这样你可以看到类似 OrderTable__root_abcd123 的类名,就能快速定位到源文件。
3. 样式重用和组件拆分策略不当引发耦合
曾经我们在封装一个通用按钮组件时,直接在 styled.button 里写了过多内联样式逻辑,结果导致组件内部样式逻辑臃肿不堪,后期维护极其困难。
优化建议:
- 将样式逻辑抽离为多个基础 style 函数或对象;
- 使用组合的方式构建组件样式;
- 尽量保持样式函数职责单一,便于复用;
六、实施效果:我们的收益和变化
经过几个月的迭代和打磨,我们将核心组件迁移到 emotion,整体效果还是相当显著的:
| 指标 | 接入前 | 接入后 | 提升幅度 |
|---|---|---|---|
| 类名冲突问题 | 高频率 | 基本消除 | ✅ |
| 主题切换支持 | 手动处理复杂 | 动态变量+插件自动处理 | ✅✅ |
| 代码可维护性 | 中等 | 更加清晰 | ✅✅ |
| 构建体积 | 不变 | 微增(可通过优化控制) | ⚠️ |
| 团队协作效率 | 较低 | 显著提升 | ✅✅✅ |
特别值得一提的是,新加入的成员能够更快地上手项目,因为样式逻辑不再分散在多个 CSS 文件中,而是紧贴组件本身,降低了上下文切换成本。
七、总结与建议:选对工具比争对立场更重要
在经历过这场 CSS 管理方式的转变之后,我的一些体会如下:
✅ 正确看待两者优劣
- 传统 CSS:轻量、兼容性好、构建快,在小型项目或静态站点中依然是性价比极高的选择;
- CSS-in-JS:提供了更高的可维护性和封装性,适合中大型 React 项目、需要动态样式的场景,但也有构建开销和调试学习成本。
✅ 实际选型应考虑这些维度
- 项目规模:小型项目建议传统 CSS;
- 协作人数/团队习惯:多人协同推荐 CSS-in-JS;
- 交互复杂度:需要大量状态驱动的样式变更推荐 CSS-in-JS;
- 性能敏感度:极致追求首屏加载速度,传统 CSS 能做得更好;
- 未来扩展:如果涉及微前端、多子系统集成,样式隔离变得至关重要。
✅ 我个人的经验之谈
如果你是刚起步的开发者,不妨先掌握传统 CSS 的基本功再谈封装;而如果你已经在一个中大型 React 项目中工作,那么真的值得花一点时间来了解并实践 CSS-in-JS。
它并不是银弹,但确实为我们在规模化开发中提供了一种更现代化的解法。
八、结语:不要纠结技术本身,而要关注解决问题的能力
回想整个过程,其实我们纠结的从来不是“哪种技术更好”,而是“哪种技术能更好地解决当下的问题”。
技术没有绝对的好坏,只有合适与否。
正如我在一次组内分享会上讲的那句话:
“我们不是为了炫技而去选择什么方案,而是为了写出让人舒服、别人看得懂、自己睡得着的代码。”
愿你在做选择的时候,也能保持这样的清醒和从容。
如你有兴趣了解更多具体项目的迁移细节、emotion 与 Tailwind CSS 的对比、或者 CSS-in-JS 的最佳实践模式,欢迎留言交流。也欢迎分享你们团队在样式管理上的经验和挑战。
共勉 😊

评论 0