CSS-in-JS 与传统 CSS:现代样式方案选择指南
引言:为什么我会关心这个问题?

在开发前端应用时,样式管理一直是个不可忽视的环节。我第一次意识到这问题的重要性,是在一个中型 React 项目里。当时我们团队使用的是传统的 CSS + SCSS 套路,但在组件逐渐增多、样式冲突频发、维护成本越来越高的情况下,开始考虑是否需要引入一种更“现代化”的方式来管理样式。于是我们就尝试了不同的 CSS-in-JS 库,比如 styled-components 和 emotion,结果发现它确实解决了很多痛点,但同时也带来了新的问题。
从那之后,我在多个项目中反复对比和评估这两种方式——传统的 CSS 和 CSS-in-JS。这篇文章将结合我实际参与的几个项目经验,聊聊它们各自的优缺点、适用场景,以及我在做出技术选型时的一些思考。
项目背景:我们的起点

事情要回溯到一年前,我当时参与了一个面向企业用户的 SaaS 平台的前端重构工作。这个项目由 React 构建,最初使用的是全局 CSS + SCSS 的方式。由于项目规模不小(超过 50 个页面),样式文件也变得非常庞大和复杂。随着新功能迭代不断推进,问题逐渐暴露出来:
- 命名冲突频繁:即使我们已经尽量用 BEM 规范命名,但依然难以避免某些类名重复。
- 样式可维护性差:修改某一块样式的副作用很难控制,经常“牵一发动全身”。
- 组件复用困难:很多 UI 组件无法直接搬去其他项目,因为样式依赖是全局的。
- 调试成本高:浏览器 DevTools 中的类名混乱,难以定位具体哪个模块的样式出了问题。
这些问题严重影响了开发效率和用户体验的一致性。为了改善这种情况,我们决定探索 CSS-in-JS 这种新兴的样式方案。
第一次尝试:从 SCSS 切换到 CSS-in-JS(styled-components)
我们先选择了 styled-components,因为它当时的社区活跃度很高,文档也很友好。切换的过程其实很快,但真正的问题是在后续开发过程中才逐步显现。
实际体验和遇到的问题
✅ 优点:
组件级封装:
- 每个组件的样式都写在 JS 文件内部,完全内聚,修改一处不会影响全局。
- 类似于 props 条件渲染的能力非常强大,比如动态计算颜色值、字体大小等。
命名唯一性自动处理:
- styled-components 自动生成唯一的 class 名称,彻底避免命名冲突问题。
- 浏览器中也能看到清晰的类名(如
.Button_asdf34a),方便调试。
开发效率提升明显:
- 因为样式紧邻组件逻辑,修改样式不再需要来回切换文件,节省了很多时间。
- 特别适合快速原型开发或小型项目。
❌ 缺点:
首屏加载性能变差:
- 尤其在 SSR 场景下,如果没有正确提取 CSS,会导致首屏 FOUC(Flash of Unstyled Content)。
- 我们早期没有优化好 SSR 渲染流程,导致页面加载慢且样式闪烁严重。
调试工具不成熟(至少那时):
- DevTools 中虽然类名有语义,但如果样式嵌套过深或者逻辑过于复杂,还是很难一眼看出问题所在。
- 现在 emotion 提供了 better debugging 支持,比之前进步不少。
学习曲线和团队协作门槛上升:
- 新同学如果不熟悉 JS 中写 CSS 的语法,会觉得“怪怪的”,甚至抗拒。
- 样式如果写得太复杂,也会出现“逻辑太重”的现象,不利于代码审查。
构建体积增加:
- styled-components 本身会带来一定的运行时开销,虽然不是致命问题,但对于追求极致性能的项目来说还是需要权衡。
转折点:从 CSS-in-JS 回归传统 CSS(但升级到了 Tailwind + PostCSS)
在某个大型项目的中期,我们又面临了一次大的架构调整机会。这时候,我们重新审视了整个样式系统的合理性,并做了大量 A/B 测试。最终我们决定放弃全面使用 styled-components,而是采用了一种更轻量级的方式:Tailwind CSS + PostCSS + SCSS 模块化组合方案。
这个转变背后有几个关键考量:
✅ 优势:
构建性能更好:
- Tailwind 是基于 PostCSS 在编译阶段进行按需打包,最终只输出实际用到的样式,减少冗余。
- 首屏加载性能大幅提升,无 FOUC。
更易掌控的样式系统:
- 所有样式都在预设的设计系统中定义好了,开发者只需使用 Utility Class。
- 大幅降低样式编写出错的可能性,提高了设计一致性。
调试依旧清晰:
- 不像 inline style 那样难读,所有类名都能在浏览器中看到对应含义,配合 Purge CSS 可以精准保留有效样式。
更好的 SSR 支持:
- 没有任何运行时依赖,服务端渲染毫无压力。
❌ 劣势:
对设计系统依赖更强:
- 如果没有统一的设计系统和严格的开发规范,很容易滥用 utility classes 导致样式失控。
- 有时候为了一个小改动,可能需要嵌套多个类,反而不如 CSS-in-JS 直接。
缺乏动态条件支持:
- 相比 styled-components,Tailwind 在条件变化时需要通过 className 字符串拼接,不够直观。
- 虽然可以通过函数抽象,但不如直接传 props 那么自然。
定制化灵活性弱一些:
- 如果项目有很多复杂的交互动态样式,Tailwind 很难满足需求,可能还是得配合局部使用 CSS-in-JS 或额外类名。
现在怎么做?我眼中的最佳实践
现在我的观点是:CSS-in-JS 和传统 CSS 各有其适用场景,不存在绝对的好坏,只有合适的与否。
下面是我目前推荐的几种情况下的选型建议:
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 快速开发的中小型项目 / 组件库 | 使用 emotion 或 styled-components |
样式封装强、便于复用,适合小团队快速上手 |
| 企业级应用 / 对性能要求较高 | Tailwind CSS + SCSS Modules 或 CSS Modules |
减少运行时负担,提高加载速度和可维护性 |
| 有大量动态交互和主题需求 | 结合使用 CSS-in-JS + Tailwind | 主体样式用 Tailwind,动态部分使用 CSS-in-JS 控制 |
| 现有遗留项目改造 | 模块化拆分 + BEM 命名 | 逐步迁移,优先保证稳定性和兼容性 |
技术之外的思考:团队协作与沟通
除了技术上的选择,还有一个我特别想强调的点是:无论你使用哪种方案,都需要一个清晰、一致的开发规范。
我们在项目初期定下了“样式必须就近编写,不得滥用全局类名”的原则,并通过代码审查和自动化工具(如 lint-staged + stylelint)强制执行。这些制度化的手段大大减少了后期因风格混乱导致的争议和返工。
此外,在面试前端候选人时,我发现很多人对 CSS-in-JS 了解不深,只是听说过。所以在团队内部我们安排了培训和 Code Review,让新人能够迅速适应这种新写法。
工具推荐 & 开发技巧分享
这里再分享一些我在实际工作中总结的小技巧和工具推荐:
- Chrome DevTools 技巧:启用
Computed标签下的“Filter styles”,可以快速找出当前元素的生效样式规则,不管是传统 CSS 还是 CSS-in-JS 都适用。 - VSCode 插件:安装
PostCSS Language Support,能智能补全 Tailwind 的 class 名称,开发效率翻倍。 - 调试 styled-components:使用
babel-plugin-styled-components插件后,浏览器中的 DOM 元素会显示清晰的组件名称,非常有助于定位问题。 - 使用 Emotion 的
css prop模式:相比 styled API 更加轻量,适合只想局部使用而不想引入整套框架的项目。

总结:适合你的才是最好的
回顾这几年来的经历,我发现样式方案的选择其实是一个“性价比游戏”。你需要根据项目的规模、团队的技术栈、性能要求、交付节奏等多个维度综合判断。
我个人的观点是:如果你正在做一个中大型项目,而且追求长期可维护性和可扩展性,那不妨试试 Tailwind + SCSS Modules 的组合;而如果你是在做组件库开发、快速 MVP、或是对样式封装要求极高的场景,CSS-in-JS 是个不错的选择。
最重要的是,不要被流行趋势绑架。技术方案的本质是服务于业务,而不是炫技。当你能清楚地理解不同方案背后的原理和局限,才能做出最合理的选择。
最后的话
前端发展太快,今天主流的技术可能明天就被淘汰。但不管怎么变,核心的目标始终没变:写出健壮、可维护、易扩展、用户体验好的产品。
希望这篇来自一线实战的经验分享,能给正在困惑如何选择样式方案的你一点启发。如果你也有类似的经历,欢迎留言交流,我们一起成长 😊

评论 0