CSS-in-JS 和传统 CSS 到底怎么选?一个被房贷压垮的北漂程序员的实战复盘
上周五晚上十一点半,我还在公司对着 MacBook Pro 的屏幕发呆。窗外深圳湾的夜景依旧璀璨,但我的眼睛已经干涩到快冒烟了。产品那边刚改完第三版 UI,要求“按钮 hover 效果要像 iPhone 那样丝滑”,而前端组里两位老哥因为用不用 CSS-in-JS 争得面红耳赤——一个说 styled-components 是未来,另一个坚持 SCSS 才是正道。我坐在中间,一边想着下个月两万八的房贷,一边默默打开 GitHub 搜了一圈开源项目的样式方案。
这事儿让我彻底意识到:选对样式方案,不只是技术问题,更是团队协作、构建性能甚至个人 sanity(理智值)的问题。
先自我介绍一下:我是坐标深圳南山科技园的一名后端转全栈程序员,在一家腾讯系公司打工。日常主力机是 Mac(Windows 只用来测 IE 兼容性,虽然现在基本没人提 IE 了),最近在啃 Rust,觉得比写 Java 爽多了。去年刚咬牙上车买了套小两居,从此每个月工资一到账,先还房贷,再点外卖——典型的“高薪穷鬼”。
今天这篇,不讲大道理,就聊聊我在真实项目里踩过的坑、熬过的夜,以及最后是怎么在 CSS-in-JS 和传统 CSS 之间做出选择的。
为什么样式方案突然成了“政治问题”?
事情起源于我们新启动的一个内部管理系统重构。旧系统是 jQuery + Bootstrap 写的,CSS 文件堆了三千多行,class 名全是 btn-primary-new-v2-fix 这种鬼东西。产品经理说要“现代化体验”,领导拍板:“用 React 重写,组件化,支持暗色模式。”
于是问题来了:怎么管理样式?
一开始,团队自然分成两派:
- CSS-in-JS 派:推崇 emotion / styled-components,认为“样式随组件走,天然隔离,还能动态主题”
- 传统 CSS 派:主张 SCSS + CSS Modules,理由是“轻量、熟悉、构建快、GitHub 上大厂项目都在用”
说实话,我原本站传统派。毕竟我可是从 PHP 时代一路写 CSS 过来的,.container { padding: 20px; } 这种代码刻进 DNA 了。但直到某天我试图给一个按钮加个根据用户权限变色的功能——用 SCSS 写了一堆 [data-role="admin"] .btn {},结果测试同学反馈“在 Safari 上颜色没生效”,我才意识到:传统 CSS 在复杂状态管理面前,真的有点力不从心。
实战对比:我拿三个典型场景试了试水
为了不被同事带节奏,我拉了个分支,分别用两种方案实现同样的功能,然后横向对比。
场景一:动态主题切换(比如暗色/亮色)
CSS-in-JS(以 emotion 为例):
import { css, ThemeProvider } from '@emotion/react'
const buttonStyle = (theme) => css`
background: ${theme.mode === 'dark' ? '#333' : '#fff'};
color: ${theme.mode === 'dark' ? '#fff' : '#000'};
`
const MyButton = () => <button css={buttonStyle}>Click</button>
配合 ThemeProvider,全局换肤只要改一个 state,所有组件自动响应。爽到飞起。
传统 CSS + CSS Modules:
// Button.module.scss
.button {
&.dark {
background: #333;
color: #fff;
}
&.light {
background: #fff;
color: #000;
}
}
JS 里得手动 toggle class:
<div className={`${styles.button} ${isDark ? styles.dark : styles.light}`}></div>
看起来也能做,但一旦组件嵌套深了,class 组合爆炸,调试时浏览器 DevTools 里全是 Button_button__1a2b3 dark__4c5d6,看得我血压飙升。
场景二:响应式断点 + 媒体查询
CSS-in-JS 写媒体查询其实挺别扭:
css`
@media (max-width: 768px) {
padding: 10px;
}
`
虽然能用,但不如 SCSS 那样直观。而且有些工具链(比如 PostCSS 插件)对 CSS-in-JS 支持不好,想用 postcss-preset-env 自动加前缀?得额外配置。
而 SCSS 里写:
.button {
@include respond-to('mobile') {
padding: 10px;
}
}
配合自定义 mixin,简直行云流水。
场景三:性能与构建体积
这才是关键!我们项目上线前做了 Lighthouse 测试。
| 方案 | 首屏 JS 体积 | CSS 提取率 | 首屏 FCP(ms) |
|---|---|---|---|
| styled-components | +18KB | ❌(内联在 JS) | 1240 |
| SCSS + CSS Modules | +2KB | ✅(独立 CSS 文件) | 890 |
CSS-in-JS 把样式打包进 JS,导致首屏 JS 体积膨胀,FCP(First Contentful Paint)明显变慢。尤其在低端安卓机上,用户看到白屏的时间更长。
不过好消息是,emotion 支持 SSR 提取静态 CSS,但配置起来有点麻烦,还得和 Next.js 或 Remix 深度集成。我们项目是纯 CSR,暂时没精力搞这个。
后端视角:为什么我开始关心前端样式?
作为半个后端,我其实一直觉得“样式是前端的事”。但自从参与了微前端架构设计,才发现:样式隔离直接影响系统稳定性。
有一次,子应用 A 引入了一个第三方 UI 库,全局 reset 了 body { font-size: 12px },结果主应用的文字全变小了,线上告警炸了。运维大哥半夜打电话骂人,我躲在被窝里瑟瑟发抖。
CSS Modules 或 CSS-in-JS 的作用域隔离能力,在这种场景下就是救命稻草。至少能保证“你的样式不会污染我的页面”。
另外,GitHub 上看大厂开源项目也挺有意思:
- Facebook 的 Draft.js 用的是传统 CSS
- Shopify 的 Polaris 用 SCSS
- Vercel 的 Next.js 官方示例 大量使用 styled-jsx(Next 自家方案)
- 而 Twitter 早期用 styled-components,后来部分迁移到 vanilla-extract(编译时 CSS-in-JS)
结论:没有银弹,只有权衡。
我们最终的选择:混合策略 + 团队共识
吵了两周后,我们开了个技术决策会,定了三条原则:
基础组件库(Button/Input/Card)用 SCSS + CSS Modules
理由:稳定、体积小、易调试、设计师给的 Figma 样式能直接转成变量业务页面中需要高度动态样式的部分,用 emotion
比如数据可视化图表的颜色根据数值动态变化,或者用户自定义主题严禁全局 CSS!所有 class 必须通过模块或 CSS-in-JS 生成
同时,我们在 ESLint 里加了规则,禁止直接写 className="my-class",必须用 styles.myClass 或 css prop。
效果立竿见影:
- 构建时间从 42s 降到 28s(CSS-in-JS 的 Babel 插件确实慢)
- 线上样式冲突 bug 归零
- 新来的实习生也能快速上手,不用猜 “这个 class 是谁写的?”
给新手的建议:别被 hype 带跑偏
如果你刚入门前端,看到网上吹“CSS-in-JS 是未来”就急着学,我劝你先冷静。
先掌握传统 CSS 的盒模型、层叠上下文、BFC、响应式原理。这些才是地基。CSS-in-JS 只是工具,不是魔法。我见过太多人连 position: sticky 都没搞懂,就开始写 styled.div,结果布局错乱还找不到原因。
另外,别为了用新技术而用。我们组有个哥们非要在登录页用 styled-components,结果就一个静态表单,硬生生多打了 50 行代码,还增加了 bundle size。产品经理看了直摇头。
最后:房贷压力下的技术选择哲学
写这篇文章的时候,我又收到了银行的还款提醒。作为一个背着三十年房贷的北漂,我越来越明白:技术选型不是追求最酷,而是追求最稳、最可持续。
CSS-in-JS 有它的高光时刻,传统 CSS 也远未过时。关键是在合适的场景用合适的工具。
GitHub 上有句话我很喜欢:“Good engineers choose the right tool. Great engineers know when not to use a tool at all.”
所以,别纠结了。打开你的编辑器,看看你的项目需求、团队习惯、性能指标,然后做个务实的选择。
毕竟,代码可以重构,但房贷……可不能 defer 啊 😅
P.S. 如果你也在深圳做前端,欢迎来科技园约咖啡(我请,只要别聊 IE 兼容性)。

评论 0