CSS-in-JS 和传统 CSS,我该选谁?
上周五晚上九点半,我家猫正趴在我键盘上打呼噜,而我在 VSCode 里疯狂切换标签页,试图搞清楚为什么这个按钮在 Safari 上莫名其妙地多了一像素边框。这已经是我本周第三次被样式问题卡住进度了——产品经理那边 deadline 明天中午就要看 demo,测试同事已经在群里@我三次问“前端联调好了没?”。
作为刚入职这家远程办公公司的试用期新人,我可不敢在这节骨眼上掉链子。毕竟转正答辩就在下个月,要是连个基础组件库都整不明白,怕是要提前开启“金三银四”求职模式了。
事情的起因其实很简单:团队要重构一个老后台系统,技术栈是 Springboot + React(别问为啥前后端混搭,问就是历史遗留)。领导说“这次要用现代化方案”,但关于样式管理方式,前端组内部吵翻了天——有人力推 CSS-in-JS,有人说“原生 CSS 够用了,别整那些花里胡哨的”。
于是,我这个“新鲜血液”被拉去调研两种方案的优劣。行吧,反正我也对前端动画和交互挺感兴趣,正好借机深入研究一下。
起手式:先搞懂它们到底是什么
很多人一听到 CSS-in-JS 就觉得是“把 CSS 写进 JS 文件里”,其实没那么简单。它本质上是一套运行时动态生成样式并注入页面的机制,最流行的库比如 styled-components、Emotion 都属于这一类。
而传统 CSS 并不单指 .css 文件,还包括 SCSS、Less 这些预处理器,以及 CSS Modules 这种局部作用域方案。咱们今天主要对比的是“全局 CSS / SCSS” vs “主流 CSS-in-JS 库”。
📌 一个小插曲:我们项目早期用的是纯 SCSS,结果某次合并代码后,两个同事写的
.button类名冲突,导致整个表单按钮变紫色(UI 设计稿里根本没有紫色!)。运维大哥看到线上报警直接在群里发了个“???”表情包。
动手实测:从零搭建两个小 Demo
为了公平起见,我分别用两种方案实现了一个带 hover 动画的卡片组件。环境如下:
- React 18
- Vite 构建(别提 Webpack 了,那玩意儿启动慢得像我早上起床)
- 浏览器:Chrome 最新版 + Safari 15(兼容性必须测!)
方案一:传统 SCSS(配合 CSS Modules)
// Card.module.scss
.card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
.title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
}
// Card.jsx
import styles from './Card.module.scss';
export default function Card({ title, children }) {
return (
<div className={styles.card}>
<h3 className={styles.title}>{title}</h3>
{children}
</div>
);
}
优点很明显:
- 熟悉,写起来飞快
- 支持所有 CSS 特性(包括
@keyframes动画) - 构建时处理,无运行时开销
- GitHub 上搜
.scss文件,一眼就能看出结构
但缺点也扎心:
- 即使用了 CSS Modules,深层嵌套的类名还是可能冲突(尤其第三方组件混用时)
- 动态主题切换得靠 CSS 变量 or 重写 class,不够灵活
- 想根据 props 改样式?要么写一堆 modifier class,要么用内联 style(丑哭)
方案二:CSS-in-JS(用 Emotion)
// Card.jsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const cardStyle = (theme) => css`
background: ${theme.bg};
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
.title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
`;
export default function Card({ title, children, theme = { bg: 'white' } }) {
return (
<div css={cardStyle(theme)}>
<h3 className="title">{title}</h3>
{children}
</div>
);
}
乍一看代码量差不多,但注意几个细节:
- 样式直接接收 JS 变量(比如
theme.bg),动态性拉满 - 不需要单独维护
.scss文件,组件自包含 - 自动处理 vendor prefix(再也不用手动加
-webkit-了!)
不过坑也不少:
- 初次加载会多一次 JS 执行时间(虽然微乎其微)
- SSR 场景下如果配置不对,会出现 FOUC(Flash of Unstyled Content)
- 在 Safari 上调试时,开发者工具里看到的是一堆 hash 类名(比如
css-1a2b3c),想手动改样式调试?难!
💡 调试技巧:Emotion 提供了
label选项,可以给生成的 class 加注释,比如css({ label: 'Card' }),这样 DevTools 里就能看到css-1a2b3c-Card,友好太多。
性能与兼容性:真刀真枪干一场
我把两个版本部署到测试环境,用 Lighthouse 跑了分,结果出乎意料:
| 指标 | SCSS + Modules | Emotion (CSS-in-JS) |
|---|---|---|
| FCP (First Contentful Paint) | 1.2s | 1.3s |
| TTI (Time to Interactive) | 2.1s | 2.3s |
| Bundle Size (gzip) | +8KB | +12KB |
| Safari 兼容性 | ✅ 完美 | ⚠️ 需 polyfill |
结论很现实:CSS-in-JS 有轻微性能损耗,但在现代设备上几乎不可感知。真正的问题在于——我们项目要支持 iOS 12 的老设备(别问,问就是客户要求),而 Emotion 的某些特性在低版本 Safari 上会崩。
这时候就得权衡了:如果你的用户都在 Chrome/Firefox/新 Safari,CSS-in-JS 完全 OK;但如果要照顾老旧浏览器,传统 CSS 更稳妥。
团队协作 & 工程化:这才是大头!
技术再酷,落地不了也是白搭。我们团队 6 个人,3 个前端,2 个刚毕业的实习生,1 个我这样的试用期“菜鸟”。大家对 CSS-in-JS 的接受度差异很大:
- 资深大佬:“JS 里写样式?污染逻辑,坚决不用!”
- 实习生:“哇!这个好炫,还能用模板字符串!”
- 我:“……能不能统一一下规范?”
于是我们做了个折中方案:
✅ 核心 UI 组件库(Button, Modal, Table)用 SCSS + CSS Modules
→ 稳定、易维护、新人上手快
✅ 业务页面中的动态/交互强的部分用 Emotion
→ 比如数据可视化图表 hover 效果、主题切换模块
顺便提一句,我们把组件库发布到了公司私有的 GitHub Packages,这样 Springboot 后台的同学也能通过 npm 引用前端组件(他们用 Thymeleaf 渲染部分页面,别笑,真的有这种混合架构)。
我的建议:别站队,按需选择
经过这两周折腾,我悟了:没有银弹,只有合适场景。
| 场景 | 推荐方案 |
|---|---|
| 快速原型 / 内部工具 | CSS-in-JS(开发效率高) |
| 对 SEO/SSR 要求高的应用 | 传统 CSS(避免 FOUC) |
| 需要深度定制主题/动态样式 | CSS-in-JS(JS 变量天然支持) |
| 团队新人多、追求稳定性 | SCSS + CSS Modules |
| 微前端架构、独立部署子应用 | CSS-in-JS(避免样式污染) |
另外,如果你正在学前端,我建议两种都掌握。我在准备跳槽时刷面经,发现大厂面试官特别爱问:“你怎么看待 CSS-in-JS?”——他们不是要你站队,而是看你是否理解工程权衡。
结语:试用期生存指南
写这篇文章的时候,我的转正 PPT 还没做完。但至少,这次样式方案之争让我在团队里露了脸——上周站会上,我展示了对比数据和 demo,leader 点头说“思路很清晰”。
远程办公最大的挑战不是技术,而是如何让别人看到你的价值。所以,哪怕是个试用期小透明,也要敢输出、敢分享。
对了,如果你也在用 Springboot + React 做全栈,或者正在纠结样式方案,欢迎来我 GitHub 仓库提 issue(链接就不放了,免得像打广告)。说不定哪天,我们还能一起写个《Springboot 前端集成避坑教程》呢 😄
最后,祝各位 coder 少点 bug,多点下班时间。毕竟,猫还在等我关电脑呢。

评论 0