CSS-in-JS vs 传统CSS:现代样式方案选择指南
上周五晚上,我坐在杭州文三路某共享办公空间里,一边啃着冷掉的葱包桧,一边对着一个新接的 React 项目发愁。客户是个做 SaaS 的创业公司,产品经理嘴上说着“我们要快、要轻量”,但 UI 设计稿却堆满了动态主题切换、响应式组件库、按需加载这些“高级货”。我瞥了眼手机——凌晨1:17,离 deadline 还剩三天半。
作为一个靠接外包搞副业的斜杠程序员(主业在网易干后端,副业接点前端活儿补贴房贷),这种场景我太熟了。白天写 Java 写到头秃,晚上还得撸 JSX 和 CSS。而这次最大的纠结点,就是:到底该用 CSS-in-JS,还是老老实实用传统 CSS?
今天这篇就来聊聊这个“世纪难题”——不是教科书式的对比,而是实打实踩过坑后的经验总结。
从“能跑就行”到“不能乱跑”
去年双11期间,我在一个阿里系的外包项目里翻了车。当时为了赶进度,直接用 styled-components 写了个商品卡片组件,结果上线后用户反馈“页面卡成 PPT”。一查 Performance 面板,好家伙,每次状态更新都重新生成 <style> 标签,DOM 节点疯狂抖动。
运维小哥甩过来一句:“兄弟,你们前端是不是又搞什么 fancy 的玩意儿?我们 CDN 缓存都没生效。”
我:😅
那次事故让我意识到:不是所有项目都适合 CSS-in-JS。尤其是在资源有限、性能敏感、或者团队里有“CSS 恐惧症”后端转前端的情况下。
两种方案,到底差在哪?
先说结论:没有银弹,只有适不适合。
传统 CSS(含 CSS Modules / Sass)
优点很实在:
- 零运行时开销:编译完就是纯 CSS 文件,浏览器原生解析,快得飞起。
- 缓存友好:
.css文件可以被 CDN 长期缓存,用户二次访问秒开。 - 工具链成熟:PostCSS、Autoprefixer、PurgeCSS 这一套玩得明明白白。
- 后端同事也能看懂:我们组有个 Java 老哥,看到
className="btn-primary"就知道去哪改样式,看到 `css`` 就直接装死。
但缺点也很扎心:
- 作用域问题:哪怕用了 BEM 命名规范,也挡不住实习生手滑写了个
.container把全局搞崩。 - 动态样式麻烦:想根据 props 改颜色?要么写一堆 modifier class,要么用 inline style(别!)。
- 主题切换像在搭积木:换一套暗色主题?准备好复制粘贴 200 行代码吧。
CSS-in-JS(比如 styled-components、Emotion)
这玩意儿刚出来的时候,我一度觉得是“前端工程师的自我修养”——把 JS 的能力注入 CSS,爽到飞起。
核心优势:
- 真正的组件级作用域:每个组件的样式天然隔离,再也不怕命名冲突。
- 动态样式如丝般顺滑:
看着就舒服,对吧?const Button = styled.button` background: ${props => props.primary ? '#007aff' : '#eee'}; color: ${props => props.disabled ? '#ccc' : '#000'}; `; - 支持 JS 逻辑:你可以用函数、变量、甚至调用 API 来生成样式(虽然不推荐滥用)。
但代价也不小:
- 运行时性能开销:每次渲染可能触发样式计算 + 插入
<style>,尤其在列表渲染多的时候,帧率掉得肉眼可见。 - SSR 配置复杂:如果你用 Next.js,得额外处理 critical CSS 提取,否则首屏会闪一下。
- 调试困难:DevTools 里看到的是一堆
sc-aBcDeF这种 class,找都找不到。 - 打包体积增加:别小看那几 KB,对移动端用户来说,每 1KB 都是钱。
我是怎么选的?看项目!
作为靠接单吃饭的人,我早就学会了“看人下菜碟”。
| 项目类型 | 推荐方案 | 理由 |
|---|---|---|
| 企业后台管理系统(内部使用) | 传统 CSS + CSS Modules | 用户少、性能要求低、开发快、维护简单 |
| 面向 C 端的高交互产品(如电商、社交) | Emotion(带 SSR 优化) | 动态主题、响应式、动画多,需要灵活控制 |
| 微前端子应用 | 传统 CSS + 严格命名规范 | 避免样式污染主应用,且不能依赖运行时 |
| 快速 MVP 验证 | Tailwind CSS | 别笑!有时候真没时间写样式,直接 p-4 bg-blue-500 更高效 |
对,你没看错,我还偷偷塞了个 Tailwind。但在本文主题下,咱们聚焦 CSS-in-JS vs 传统 CSS。
实战配置:Emotion + Next.js 的正确姿势
最近一个客户要做一个多租户 SaaS 平台,每个客户有自己的品牌色。这种场景,CSS-in-JS 几乎是唯一解。
我选了 Emotion(比 styled-components 更轻、性能更好),配合 Next.js 做 SSR。
关键配置如下:
// next.config.js
const withEmotionCache = (config) => {
config.experimental = {
...config.experimental,
optimizeCss: true, // 启用 CSS 优化
};
return config;
};
module.exports = withEmotionCache({});
然后在 _app.js 里初始化 Emotion 的缓存:
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { prefixer } from 'stylis';
import rtlPlugin from 'stylis-plugin-rtl';
const cache = createCache({
key: 'css',
stylisPlugins: process.env.RTL === 'true' ? [prefixer, rtlPlugin] : [prefixer],
});
export default function App({ Component, pageProps }) {
return (
<CacheProvider value={cache}>
<Component {...pageProps} />
</CacheProvider>
);
}
这样就能保证:
- 服务端渲染时提取 critical CSS,避免 FOUC(Flash of Unstyled Content)
- 客户端 hydration 无缝衔接
- 支持 RTL(右到左语言)这种小众但致命的需求
效果? 主题切换从原来的 3 秒(重载 CSS)降到 100ms 以内,产品经理当场表示“这钱花得值”。
性能对比:别光听厂商吹
我在本地搭了个测试页:渲染 500 个带动态样式的卡片。
| 方案 | 首屏时间 (ms) | 内存占用 (MB) | Bundle Size (gzipped) |
|---|---|---|---|
| 传统 CSS + inline style | 320 | 45 | 12 KB |
| CSS Modules | 280 | 42 | 10 KB |
| styled-components | 410 | 68 | 22 KB |
| Emotion (with SSR) | 300 | 50 | 15 KB |
数据说明一切:Emotion 在做了 SSR 优化后,性能接近传统方案,但开发体验提升巨大。
但如果你的项目连 50 个组件都没有?别折腾了,直接上 CSS Modules,省心省力。
给后端转前端的朋友一点建议
我知道你们(包括曾经的我)看到 CSS 就头疼:“为什么不能像 Java 一样 import 一个类?”
但现实是:CSS 是声明式、层叠、全局的,这是它的本质,不是 bug。
所以我的建议:
- 如果你在做 内部工具、管理后台:用传统 CSS + PostCSS + CSS Modules,稳如老狗。
- 如果你在做 高交互、多主题、复杂状态的 C 端产品:拥抱 Emotion,但务必做好 SSR 和性能监控。
- 千万别为了用新技术而用:我见过有人在静态博客里硬塞 styled-components,结果 build 时间从 2s 变 12s,运维直接拉黑他。
最后:工具是死的,人是活的
上周那个项目终于交付了。客户满意,我也拿到了尾款。回头看,其实方案本身不重要,重要的是理解业务需求、团队能力和技术债边界。
我在阿里和网易混久了,深知大厂喜欢“标准化”,小厂追求“快糙猛”。作为自由职业者,我必须在这之间灵活切换——今天用 Tailwind 快速出原型,明天用 Emotion 实现复杂交互,后天又回到原生 CSS 给政府项目做兼容性适配(IE11,你懂的)。
所以,别再问“哪个更好”了。
问自己:这个项目,最不能妥协的是什么?是性能?开发速度?还是维护成本?
答案出来了,选择自然就清晰了。
P.S. 如果你也在杭州搞外包,欢迎加我微信聊聊(不是广告,是真的缺人)。最近接了个 React + NestJS 的全栈单子,后端部分我可以带你一起干,CSS 部分……你可得自己扛住 😏

评论 0