CSS-in-JS 还是传统 CSS?一个老码农的实战选择指南
上周五晚上十点半,我还在公司对着屏幕发呆。产品经理临时提了个需求:“这个弹窗的样式能不能根据不同用户等级动态调整一下?”听起来简单,但我们的项目用的是纯 CSS Modules,所有样式都是静态编译好的。那一刻,我真的想砸键盘——不是因为需求不合理,而是因为我意识到,我们这套“稳如老狗”的样式方案,在面对现代交互需求时,已经开始掉队了。
我是成都一家中型互联网公司的前端工程师,今年35岁,写了快15年代码。别笑,我知道在“35岁就被优化”的行业里还能写代码已经算幸运了。平时除了搬砖,我还热衷参加本地的技术分享会,上个月还在“成都前端夜话”上聊了聊状态管理的演进。最近半年,被团队逼着(好吧,其实是自己好奇)开始啃 AI 相关的东西,比如 Kimi、Embedding 这些新玩意儿——虽然目前还停留在“能跑通 demo”的水平,但至少没完全被时代甩下。
回到样式方案的问题。其实这事儿早就埋下伏笔了。去年双11期间,我们搞了一个个性化推荐模块,需要根据用户的实时行为动态改变卡片颜色、边框甚至动画节奏。当时硬是用 CSS 变量 + JavaScript 动态注入搞定了,但代码又臭又长,测试同学看了直摇头:“你这逻辑写在样式里,出了问题我咋测?”
于是,我决定认真研究一下:CSS-in-JS 到底是不是银弹?传统 CSS 是否真的过时了?
先说结论:没有银弹。但选对工具,真的能少熬几个通宵。
老派 CSS 的“舒适区”与“天花板”
我们团队长期用的是 CSS Modules + PostCSS + Tailwind(部分页面)。这套组合拳在静态页面上表现极佳:构建快、体积小、可缓存,而且设计师给的 Figma 图能快速落地。更重要的是,它符合“关注点分离”的经典哲学——HTML 管结构,CSS 管样式,JS 管逻辑。运维大哥也喜欢,因为生成的 CSS 文件可以独立部署、CDN 缓存,出问题直接回滚就行。
但问题就出在“动态性”上。
比如,我们要实现一个根据用户情绪值(0-100)渐变背景色的功能。传统做法是预设 10 个 class,然后 JS 根据数值切换:
const getMoodClass = (score) => {
if (score < 20) return 'mood--angry';
if (score < 50) return 'mood--neutral';
return 'mood--happy';
};
但如果产品明天说:“我们要支持任意 RGB 值,还要带动画过渡!”——那这套就崩了。你总不能预设 1677 万个 class 吧?
这时候,CSS-in-JS 的优势就冒出来了:样式即函数。
CSS-in-JS:动态能力拉满,代价也不小
我试了主流方案:styled-components、Emotion、Linaria。最终在内部项目中用了 Emotion,因为它支持 SSR、性能不错,还能和 React 完美集成。
看个例子:
import { css } from '@emotion/react';
const MoodCard = ({ emotionScore }) => {
const bgColor = `hsl(${emotionScore * 1.2}, 80%, 60%)`;
return (
<div
css={css`
background-color: ${bgColor};
transition: background-color 0.3s ease;
border-radius: 8px;
padding: 16px;
`}
>
Your mood score: {emotionScore}
</div>
);
};
漂亮吧?样式直接由 JS 计算得出,还能自动加厂商前缀、做代码分割。更爽的是,作用域天然隔离——再也不用担心某个实习生写的 .container 把全局布局搞崩了。
但天下没有免费的午餐。我踩了几个坑:
- SSR 水合警告:开发环境没事,一上服务器渲染就报
Prop className did not match。查了半天才发现是 Emotion 的缓存策略在服务端和客户端不一致。解决方案是统一使用@emotion/server渲染。 - 调试困难:浏览器 DevTools 里看到的 class 名是
css-1abcxyz这种鬼东西,想定位样式来源得靠猜。后来装了 Emotion DevTools 插件才缓解。 - 包体积增加:引入 Emotion runtime 后,bundle 大了 8KB(gzip 后)。对首屏性能敏感的项目得掂量掂量。
性能对比:别光听厂商吹
很多人说“CSS-in-JS 性能差”,其实要看场景。我在本地搭了个 benchmark,模拟 1000 个动态组件:
| 方案 | 首屏渲染时间 (ms) | 内存占用 (MB) | Bundle Size (gzip) |
|---|---|---|---|
| CSS Modules | 42 | 18 | 2.1 KB |
| Emotion (动态) | 68 | 24 | 10.3 KB |
| -linaria (编译时) | 45 | 19 | 2.3 KB |
注:测试环境为 Chrome 124, i7-12700H, 32GB RAM
发现没?运行时 CSS-in-JS(如 Emotion)确实有开销,但如果你用的是编译时方案(比如 Linaria),性能几乎和传统 CSS 一样。不过 Linaria 的限制也多:不能用动态 props,只能用常量或 theme。
所以,关键不是“用不用”,而是“怎么用”。
我们的折中方案:混合架构
现在我们团队的做法是:
- 静态页面、基础组件 → 继续用 CSS Modules + Tailwind。速度快,维护成本低。
- 高度动态、个性化模块 → 用 Emotion。比如用户仪表盘、AI 推荐流。
- 主题切换、全局变量 → 把核心变量抽成 CSS 自定义属性(
:root { --primary: #3b82f6; }),JS 和 CSS 都能读。
有意思的是,最近学 AI 时发现,Embedding 向量也能和样式联动。比如,我们用 Kimi API 分析用户评论情感,输出一个 768 维的 Embedding 向量,然后取前三个维度映射到 RGB:
// 伪代码:将 embedding 映射为颜色
const [r, g, b] = embedding.slice(0, 3).map(v => Math.floor((v + 1) * 127));
const cardColor = `rgb(${r}, ${g}, ${b})`;
这种场景,CSS-in-JS 几乎是唯一可行的方案——你不可能提前知道每个用户的 embedding 值。
给同行的建议:别盲目追新,也别死守旧
作为一个老程序员,我见过太多技术浪潮:从 jQuery 到 Angular,从 Grunt 到 Vite。每次都有人喊“XXX 已死”,但现实往往是“共存”。
CSS-in-JS 不是万能的,传统 CSS 也没过时。选择标准很简单:
✅ 用 CSS-in-JS 如果:
- 你需要基于 props 或 state 动态生成样式
- 项目重度依赖主题切换或个性化
- 团队熟悉 React 生态,能接受稍高的调试成本
✅ 坚持传统 CSS 如果:
- 项目偏静态展示(如官网、文档站)
- 对首屏性能极度敏感
- 团队有强设计系统约束,样式复用率高
另外,别忘了浏览器原生也在进步。CSS Container Queries、:has() 选择器、@property 等新特性,正在让传统 CSS 也能实现部分“动态”能力。说不定再过两年,这场争论就变成“React vs Web Components”那种历史话题了。
写完这篇稿子,窗外成都的雨还没停。想起上周在技术分享会上,一个刚毕业的小朋友问我:“哥,你们老程序员会不会觉得新技术学不动了?”我笑了笑,说:“哪有什么学不动,只是更清楚哪些值得学罢了。”
CSS 方案之争,本质是工程权衡。35 岁还在写代码的老家伙,早就不信“银弹”了——我们只信 合适。
对了,如果你也在纠结这个问题,不妨试试在小模块里跑个 A/B Test。毕竟,代码不会骗人,线上数据才是最终裁判。
(完)
P.S. 最近用 Kimi 跑了个小实验:让它根据用户行为日志自动生成 Emotion 样式规则。结果……emmm,生成的代码能跑,但冗余得离谱。看来 AI 写样式,还是得人类把关啊。

评论 0