CSS-in-JS vs 传统CSS:现代样式方案选择指南
上周五晚上,我正躺在公司附近那个15㎡的出租屋里刷B站,突然钉钉“叮”一声——产品经理发来消息:“明天上线前能不能把主题色切换功能加上?” 我看了一眼时间:23:47。心里一万只草泥马奔腾而过,但嘴上还得回个“好的”。
这事说来也巧。我们组最近在重构一个内部管理后台,技术栈是 React + TypeScript。之前用的是老派 SCSS + CSS Modules,结果现在要支持动态换肤,直接给我整不会了。硬着头皮翻文档、看 GitHub issue、甚至去 Stack Overflow 挖坟,才意识到:样式方案这事儿,真不是写几个 class 就完事的。
所以今天这篇,就结合我这个二线互联网公司“三年前端老油条”的实战经验,聊聊 CSS-in-JS 和传统 CSS 到底怎么选。不搞玄学,只讲项目里真实踩过的坑。
起因:一个看似简单的“换肤”需求
我们系统原本只有默认蓝灰配色。产品经理说,运营同学想根据不同节日(比如双11、春节)快速切换 UI 主题。听起来人畜无害对吧?但当我打开 src/styles/variables.scss 那一刻,就知道事情没那么简单。
传统 CSS 的痛点在这类场景下暴露无遗:
- 所有颜色都写死在
.scss文件里,没法在 JS 运行时动态改 - 即使用
:root定义 CSS 变量,也要手动遍历 DOM 注入style标签,维护成本爆炸 - 更别说有些组件用了内联 style(别问,问就是历史包袱),和全局样式打架
去年双11期间,我就因为类似问题被线上告警 call 起来三次。那次是改了个按钮 hover 效果,结果影响了另一个页面的弹窗动画——CSS 的全局污染,真的会杀人。
入坑 CSS-in-JS:从抗拒到真香
说实话,一开始我对 CSS-in-JS 是拒绝的。总觉得“JS 里写 CSS”很反人类,而且听说性能差(后来发现是早期实现的问题)。但被 deadline 逼到墙角,只能硬着头皮试了 Emotion ——毕竟它和 React 生态融合得不错,GitHub star 也够多。
快速上手:三行代码搞定主题切换
装包:
npm install @emotion/react @emotion/styled
在 App.tsx 顶层包裹 ThemeProvider:
// src/App.tsx
import { ThemeProvider } from '@emotion/react';
const App = () => {
const [theme, setTheme] = useState('light'); // 或 'dark', 'festival' 等
return (
<ThemeProvider theme={themes[theme]}>
{/* 你的组件 */}
</ThemeProvider>
);
};
然后在组件里直接消费主题变量:
// src/components/Button.tsx
import styled from '@emotion/styled';
const StyledButton = styled.button`
background-color: ${props => props.theme.primaryColor};
border: 1px solid ${props => props.theme.borderColor};
&:hover {
opacity: 0.9;
}
`;
看到没?主题色直接从 JS 对象里取,完全不用碰 CSS 文件。产品经理要加个“圣诞红”主题?我只要在 themes.ts 里新增一个对象,10 分钟搞定。那天晚上 1 点提交代码时,我甚至对着屏幕笑出了声——终于不用再和 SCSS 变量名斗智斗勇了。
但!CSS-in-JS 真的完美吗?
别急着吹。用了三个月后,我在一次性能 review 中被 QA 同事灵魂拷问:“为什么首屏 LCP 比隔壁组慢 300ms?”
查了半天,发现 Emotion 在 SSR(服务端渲染)时会把所有样式 inline 到 HTML 里,导致初始 HTML 体积膨胀。虽然客户端 hydration 后会优化,但对 SEO 和首屏体验还是有影响。
更坑的是浏览器兼容性。我们有个客户还在用 IE11(别问,金融行业你懂的),而 Emotion 默认生成的 CSS 包含 :not(:-webkit-any) 这种 hack,直接让 IE 崩溃。最后不得不加 babel 插件降级,还写了 polyfill。
另外,调试体验也两极分化:
- 好处:VSCode 装了 vscode-styled-components 插件后,语法高亮+智能提示爽到飞起
- 坏处:Chrome DevTools 里看到的 class 名是
css-1a2b3c4这种 hash,排查样式覆盖问题时得靠猜(虽然可以配labelFormat,但团队里没人记得开)
回归理性:什么场景该用哪种方案?
经过这轮折腾,我和组长开了个技术复盘会,总结出一套“决策树”。分享给各位兄弟,避免重蹈我的覆辙:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 高度动态 UI(如主题切换、运行时配色) | CSS-in-JS (Emotion / styled-components) | JS 直接控制样式,逻辑集中 |
| 静态营销页 / 内容型网站 | 传统 CSS (SCSS + CSS Modules) | 无状态,打包后体积小,SEO 友好 |
| 微前端架构 / 多团队协作 | CSS Modules + BEM 命名 | 避免样式冲突,边界清晰 |
| 需要极致性能(如电商首页) | 传统 CSS + Critical CSS 内联 | 减少 JS 解析开销,首屏更快 |
| 组件库开发 | CSS-in-JS + 传统 CSS 双输出 | 既支持动态定制,又保留静态引用能力 |
💡 我们现在的策略:核心业务用 Emotion,静态页面用 SCSS。通过 Webpack 配置按需加载,互不影响。
给新手的建议:别为了新而新
刚入行那会儿,我也迷信“新技术一定更好”。结果在第一个项目里强行上 styled-components,结果 build 时间翻倍,还因为没处理好 SSR 被运维大哥追着骂。
现在带实习生,我第一句话就是:“先搞清楚需求,再选工具”。
如果你的项目只是做个博客或者后台表格,传统 CSS 完全够用,别给自己加戏。CSS Modules 配合 BEM 命名,已经能解决 90% 的作用域问题。而且团队里非前端同事(比如切图的设计师)也能看懂 .button--primary 是啥意思,沟通成本低。
但如果你在做 Design System、需要支持深色模式、或者用户能自定义 UI,那 CSS-in-JS 的优势就压倒性了。关键是把样式当作“可编程的数据”,而不是“静态资源”。
最后:工具没有银弹,只有合适
写这篇文章时,窗外上海的梅雨季又开始了。想起三年前我刚入职这家公司,连 Flex 布局都要查 MDN。现在虽然能侃侃而谈 CSS-in-JS 的优劣,但每次遇到样式 bug,还是会默默打开 Chrome DevTools 的 Styles 面板,一行行勾选排查。
技术选型这事儿,从来不是非黑即白。CSS-in-JS 不是银弹,传统 CSS 也没过时。重要的是理解背后的设计哲学:前者追求“逻辑与样式的统一”,后者坚持“关注点分离”。
下次当产品经理再半夜找你改样式,别慌。先问清楚需求本质,再决定是打开 styles.scss 还是写个 styled.div。毕竟,我们前端的终极目标,不是炫技,而是——早点下班。
(完)
P.S. 如果你也在用 Emotion,记得开启
sourceMap和autoLabel,调试时能救命。
P.P.S. 租房合同快到期了,求推荐浦东靠谱小区…预算 6k 以内,离地铁近就行 😭

评论 0