CSS-in-JS 与传统 CSS:我的现代样式方案选择实战指南
引言:为什么我会去思考这个问题?

大家好,我是张工,从事前端开发已经有七八年了。最近几年,我参与了一些中大型前端项目重构工作,也负责过多个从0到1的项目搭建。在这个过程中,有一个问题始终伴随着我:在现代前端项目中,到底该用 CSS-in-JS 还是传统的 CSS 解决方案?
起初我以为这是一个“伪命题”——毕竟不就是写个样式嘛,怎么方便怎么来。但随着团队人员变化、项目规模扩大、功能迭代频繁,我逐渐意识到,这其实是一个直接影响开发效率、可维护性和团队协作方式的技术选型问题。
今天我就结合几个真实项目经历,和大家一起聊聊我在不同项目场景下如何做这个取舍,以及踩过的坑、收获的经验,希望能对你有所帮助。
我遇到的第一个挑战:组件封装 + 团队协同带来的样式管理难题


背景介绍
2021 年我加入了一个内部工具平台项目,目标是打造一个统一的 UI 组件库供公司多个业务线使用。一开始我们选择了标准的做法:CSS Modules + Sass。结构简单明了,每个组件目录里都有 index.js 和 style.module.scss 文件。
看起来很完美对吧?但没过多久就遇到了问题:
- 不同开发人员写的类名冲突了(明明用了 CSS Modules!)
- 样式覆盖严重,经常要手动添加
!important或者提高优先级 - 修改样式需要同步修改
.scss文件和 JS 中引入的类名,容易出错 - 新人上手慢,理解类名映射关系不够直观
- 某些组件希望根据 props 动态调整样式,这时候传统的 CSS 写法显得力不从心
我们尝试的第一种方案:继续优化模块化体系
我们试过一些手段来缓解这些问题,比如更严格的命名规范、建立共享变量库、用 BEM 来提升类名可读性……但这些都治标不治本。
真正让我开始重新审视这个问题的,是当我们需要实现一个高度定制化的表格组件,它允许用户通过配置项动态控制列宽、颜色、行高、hover 效果等等。这个时候,单纯靠写死的 CSS 类已经很难满足需求了。
我开始调研市面上主流的 CSS-in-JS 方案:styled-components、emotion、goober……
最终我们选择了 emotion,因为它支持 React 的 @emotion/styled 语法,也能很好地配合 Typescript,而且性能在当时来看表现不错。
CSS-in-JS 是灵丹妙药吗?让我们用代码说话

示例:传统 CSS 写法 vs CSS-in-Js 写法
假设我们要做一个按钮组件,支持传入颜色、大小、是否禁用等 props,并生成对应的样式。
传统 CSS + CSS Modules 写法
// Button.jsx
import styles from './Button.module.css'
function Button({ color = 'primary', size = 'medium', disabled, children }) {
const className = `${styles.button} ${styles[color]} ${styles[size]}`
return (
<button className={className} disabled={disabled}>
{children}
</button>
)
}
/* Button.module.css */
.button {
padding: 8px 16px;
border-radius: 4px;
}
.primary {
background-color: blue;
color: white;
}
.secondary {
background-color: gray;
color: black;
}
.medium {
font-size: 14px;
}
.large {
font-size: 16px;
}
这种方式的优点很明显:清晰易懂、利于调试。但缺点也很明显:
- 样式定义和逻辑完全分离,容易迷失在文件夹之间
- 随着状态增多,JSX 中的 class 拼接变得难以维护
- 所有样式都要预先写好,无法根据 props 实时计算生成
Emotion styled API 写法
// Button.jsx
import styled from '@emotion/styled'
const StyledButton = styled('button')(
{
padding: '8px 16px',
borderRadius: 4,
fontSize: props => (props.size === 'large' ? '16px' : '14px'),
backgroundColor: props => (props.color === 'primary' ? 'blue' : 'gray'),
color: props => (props.color === 'primary' ? 'white' : 'black'),
},
props => props.disabled && {
opacity: 0.5,
pointerEvents: 'none'
}
)
function Button({ color = 'primary', size = 'medium', disabled, children }) {
return (
<StyledButton color={color} size={size} disabled={disabled}>
{children}
</StyledButton>
)
}
这段代码看起来是不是清爽很多?虽然写法有点“奇怪”,但优点显而易见:
- 样式紧随组件定义,阅读体验更好
- 可以直接使用 props 值动态生成样式值
- 无需再维护一堆 .module.css 文件,减少文件数量
- Emotion 会自动处理 classname 映射和缓存机制,不用担心冲突
使用 CSS-in-JS 遇到的坑

别急,我也不是说 CSS-in-JS 就没有问题。在实际项目中我们确实踩了不少坑:
💣 性能问题(特别是在 SSR 项目中)
刚开始我们在一个 Next.js 项目的 SSR 页面中大量使用 emotion,结果构建速度骤降,首屏加载明显变慢。后来发现是因为:
- 每个带有样式的组件都会注入
<style>标签,SSR 阶段合并困难 - 服务端渲染时 emotion 默认不会进行缓存,导致每次请求都需要重新生成样式
解决方案:
- 升级 emotion 到 v11+,启用
@emotion/server插件,在服务器端预提取所有样式标签 - 配置 webpack 缓存策略
- 在 Next.js 中使用官方推荐的
_document.tsx注入方式
💥 可调试性问题
有些人说 CSS-in-JS 不如传统 CSS 容易调试。这话有一定道理。比如你看到页面上的某个元素样式不对,但在浏览器 DevTools 里只能看到类似 .css-1a2b3c 的类名,不知道具体来自哪个组件、哪条语句。
解决办法:
- 开启 emotion 的 label 支持:
label选项可以让生成的类名包含组件名或样式对象的 key - 使用 Chrome 插件,比如 React Developer Tools,直接定位到对应组件
- 对关键样式部分尽量命名,便于排查
const Container = styled.div({
// 自动带标签
}, 'Container')
🧊 主题系统兼容性和嵌套太深的问题
我们一开始希望通过 emotion 构建一套主题系统,用来集中管理配色、字体、间距等设计变量。但在某些深度嵌套的子组件中访问 theme 出现了问题,主要是:
- 子组件没有被正确包裹在 ThemeProvider 下
- 状态传递中断或者未按预期解析
经验总结:
- 确保整个应用都在 ThemeProvider 包裹范围内(通常是根组件)
- 主题变量要扁平化设计,避免嵌套层级过深
- 可以结合
ThemeProvider与 context 机制做增强封装
我们也没有抛弃传统 CSS:合适才是最好的
可能你会问:“那你现在还用传统 CSS 吗?”
答案是:当然用啊!
举个例子:一个轻量级营销网站的静态页
去年年底我们接手了一个企业官网类项目,页面内容相对静态,SEO 要求较高,整体交互也不复杂。这时候我们果断放弃了 CSS-in-JS,选择了:
PostCSS+TailwindCSS(用于快速布局)SCSS(用于部分自定义组件样式)PurgeCSS(清理冗余类名)autoprefixer(适配浏览器前缀)
这样的组合好处很明显:
- 构建速度快,几乎没有运行时负担
- SEO 表现优秀(CSS-in-JS 的 SSR 处理始终不如原生友好)
- Tailwind 提供了极其丰富的 utility class,非常适合这种展示类页面
- 所有样式都在构建阶段编译完成,生产环境性能极佳
而且说实话,对于这类偏静态的页面,CSS-in-JS 的优势其实并不明显。
我是怎么做技术选型的?
说了这么多,我想你也看出来了:不存在绝对的对与错,只有“适合与否”。
在我现在的认知中,技术选型应该围绕以下几个维度展开:
| 维度 | 传统 CSS(包括 SCSS、CSS Modules) | CSS-in-JS |
|---|---|---|
| 项目类型 | 展示型页面、静态站点、SEO敏感型 | 动态性强的组件、SPA 应用 |
| 样式复杂度 | 静态样式为主,无太多条件分支 | 需要根据 props/状态动态生成样式 |
| 维护成本 | 文件略多,样式分散 | 样式集中,学习成本稍高 |
| 性能 | 构建阶段完成,运行时无开销 | 运行时注入样式,需注意 SSR 兼容 |
| 调试难度 | 可读性好,便于审查 | 类名抽象,依赖 DevTools |
| 工具链成熟度 | 成熟稳定,社区资源丰富 | 生态活跃但仍有碎片化风险 |

我的真实建议:
- 如果你在做一个 企业官网 / 文档网站 / 登录页等展示性质强的内容页,那强烈建议使用传统 CSS(特别是 TailwindCSS + PostCSS 的组合),性能和 SEO 表现会让你惊喜。
- 如果你在开发一个 复杂的 SPA 应用 / UI 组件库 / 动态数据驱动的交互界面,那 CSS-in-JS 会大大提升你的开发效率和可维护性。
- 如果你是小团队创业项目,建议前期采用传统 CSS 快速开发,后期逐步过渡;如果是大厂或长期维护的项目,可以一开始就考虑引入成熟的 CSS-in-JS 方案。
最后想分享的几点经验
✅ 真实心得一:不要把 CSS-in-JS 当成炫技
我曾经在一个技术分享会上看到有人炫技似的写出了一堆嵌套函数、层层推导出来的样式属性,看似灵活,其实让人根本看不懂。代码的可读性和可维护性永远比“技巧酷炫”更重要。
✅ 真实心得二:不要忽视浏览器兼容性和性能表现
尤其是在国内环境下,IE11 或低版本手机浏览器仍然需要支持的情况下,CSS-in-JS 可能会有意想不到的表现。一定要做好兜底测试。
✅ 真实心得三:文档 & 规范才是团队协作的核心
无论你选哪种方案,都需要制定良好的编码规范,并保持一致的风格。否则哪怕你用的是最流行的 styled-components,项目也会很快陷入混乱。
总结:选择没有标准答案,关键是理解和匹配
这篇文章写到这里,我想表达的核心其实只有一句话:
“没有银弹,只有因地制宜。”
技术的选择从来不是非黑即白的判断题,而是不断探索、试错、权衡的过程。
不管是传统 CSS,还是 CSS-in-JS,它们都只是工具。重点在于你能否清楚地理解它们的优劣势,并根据当前项目的特点做出最适合的选择。
如果你还在纠结样式该怎么写,不妨先试着回答这几个问题:
- 我正在做的这个项目是偏展示类的,还是交互性特别强?
- 是否需要频繁地基于 props 或 state 动态改变样式?
- 我的团队是否有能力维护较为复杂的样式工程方案?
- 是否对 SEO 或性能有特殊要求?
我相信只要你搞清楚这些问题,就能找到自己的答案。
如果你也在实际项目中做过类似的选型决策,或者想跟我交流某一种方案的具体落地细节,欢迎留言一起探讨。前端的世界一直在变,但我们的目标始终不变 —— 写出优雅、健壮、可维护的代码。
共勉 🙌

评论 0