CSS-in-JS 还是传统 CSS?一个老码农的实战选择指南

呆呆企鹅
2026-05-08 23:04
阅读 3562

上周五晚上十点半,我还在公司对着屏幕发呆。产品经理临时提了个需求:“这个弹窗的样式能不能根据不同用户等级动态调整一下?”听起来简单,但我们的项目用的是纯 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 把全局布局搞崩了。

但天下没有免费的午餐。我踩了几个坑:

  1. SSR 水合警告:开发环境没事,一上服务器渲染就报 Prop className did not match。查了半天才发现是 Emotion 的缓存策略在服务端和客户端不一致。解决方案是统一使用 @emotion/server 渲染。
  2. 调试困难:浏览器 DevTools 里看到的 class 名是 css-1abcxyz 这种鬼东西,想定位样式来源得靠猜。后来装了 Emotion DevTools 插件才缓解。
  3. 包体积增加:引入 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

最热最新
暂无评论
呆呆企鹅Lv.1
0
影响力
0
文章
0
粉丝