CSS-in-JS vs 传统CSS:现代样式方案选择指南

朱浩宇
2025-12-13 02:48
阅读 611

上个月,我在公司内部搞了一次技术分享,主题就是“前端样式到底该怎么写”。结果刚讲完,隔壁组的后端同事跑过来问我:“你们前端是不是闲得慌?连写个样式都要搞出十几种方案?”我只能苦笑——这事儿还真不是我们想折腾,而是业务逼的。

我是某上市公司技术中台团队的老油条了,待了三年多,每天一边听着 Lo-fi Beats 写代码,一边琢磨着要不要换个环境。最近半年,我们团队接手了一个新项目:一个面向企业客户的可视化配置平台,要求高复用、强隔离、支持动态主题切换,甚至还要考虑未来和区块链数据看板集成(别问,问就是产品总监看了某白皮书突发奇想)。就在这个节骨眼上,样式方案成了争论焦点——到底是继续用传统的 .css 文件,还是全面拥抱 CSS-in-JS?

说实话,去年双11大促前,我们还因为样式冲突导致某个组件在生产环境“裸奔”了两个小时。当时产品经理在群里@我:“用户说按钮变透明了,还能点吗?”我盯着满屏的 !important 和莫名其妙的全局类名,真的想砸电脑。


起因:一次“主题切换”的灾难

事情的导火索是产品提了个需求:支持多租户动态主题切换,不同客户登录后看到的 UI 颜色、间距、字体统统不一样。而且,这些主题信息是从后端 API 动态拉取的——这意味着我们不能在构建时预设所有样式。

最初,我们尝试用 SCSS + CSS Variables 搞定。想法很美好:

:root {
  --primary-color: #007aff;
  --border-radius: 4px;
}

.button {
  background: var(--primary-color);
  border-radius: var(--border-radius);
}

然后在 JS 里动态更新:

document.documentElement.style.setProperty('--primary-color', tenantTheme.primaryColor);

看起来很 clean 对吧?但问题很快暴露:

  1. IE 不支持 CSS Variables(虽然现在 IE 用户少,但我们的政企客户还在用)
  2. 复杂组件(比如带 hover、focus、disabled 状态的下拉框)状态样式难以维护
  3. 更致命的是——样式泄露。某个小组件不小心用了全局类名 .card,结果被另一个团队的 .card:hover 覆盖了,线上直接炸了。

运维小哥半夜打我电话:“兄弟,监控显示错误率飙升,是不是你下午上线那个‘主题’搞的鬼?”
我:“……大概吧。”


探索 CSS-in-JS:从抗拒到真香

被线上事故教育后,团队决定认真评估 CSS-in-JS。我一开始是抗拒的——毕竟写了十年 CSS,总觉得把样式塞进 JS 里是“邪道”。但现实逼人成长啊。

我们重点试了两个主流库:styled-componentsEmotion。选它们是因为社区活跃、TypeScript 支持好,而且我们用的 React 技术栈天然契合。

先看一个 Emotion 的例子:

import { css, jsx } from '@emotion/react';

const Button = ({ variant, children }) => (
  <button
    css={css`
      padding: 8px 16px;
      border-radius: ${variant === 'rounded' ? '20px' : '4px'};
      background: ${theme.primaryColor}; // 来自动态主题上下文
      &:hover {
        opacity: 0.9;
      }
    `}
  >
    {children}
  </button>
);

优势立马显现:

  • 作用域隔离:每个组件样式自动加了唯一 hash,再也不怕 .button 被覆盖
  • 动态性极强:props 和 theme 直接注入样式,不用再拼字符串或操作 DOM
  • 逻辑与样式共存:条件样式写起来比 SCSS 的 @if 清晰多了

但坑也不少。最头疼的是性能问题。有一次我在一个循环渲染的列表里用了 styled-component,结果页面卡成 PPT。Chrome DevTools 一看,每帧都在生成新的 <style> 标签——原来我没用 shouldForwardProp 过滤掉非 DOM 属性,导致每次 props 变化都触发重渲染。

后来学乖了,关键路径上尽量用静态样式,动态部分抽离成独立 hook:

// theme/useDynamicStyle.ts
export const useButtonStyle = (variant: string) => {
  return css`
    border-radius: ${variant === 'rounded' ? '20px' : '4px'};
    // ...其他动态逻辑
  `;
};

对比实测:传统 CSS 真的过时了吗?

为了说服保守派同事(以及我自己),我搞了个小 benchmark。场景很简单:渲染 1000 个带 hover 效果的卡片,分别用三种方案:

  1. 纯 CSS + class 切换
  2. CSS Modules
  3. Emotion (with cache)

结果如下(本地 MacBook Pro, Chrome 124):

方案 首屏 FCP (ms) 内存占用 (MB) Bundle Size 增量
纯 CSS 85 42 +0KB
g CSS Modules 92 44
Emotion 118 58 +18KB

注:FCP = First Contentful Paint,越低越好

结论很明显:传统 CSS 在性能上依然有优势,尤其是对静态内容。但如果你需要大量运行时动态样式(比如我们的主题系统),CSS-in-JS 的开发体验和维护成本优势就碾压了。

另外提一嘴兼容性:CSS Modules 能完美支持到 IE11(只要配好 postcss-loader),而 Emotion 默认只到 IE11+,且需要额外 polyfill。不过对我们这种 ToB 产品来说,客户浏览器版本可控,倒也不是大问题。


区块链项目的特殊考量

说到区块链,你可能觉得和前端样式八竿子打不着。但我们最近确实在对接一个链上数据看板项目——用户要实时查看 NFT 交易记录,界面元素会根据链上事件动态变色(比如大额交易标红)。

这种场景下,传统 CSS 几乎没法做:

  • 无法预知哪些元素需要高亮
  • 高亮规则来自智能合约返回的数据
  • 用户可能同时看多个链,主题切换频率极高

最后我们用 Emotion + React Context 搞定:

// ThemeProvider 封装
<ThemeProvider theme={dynamicChainTheme}>
  <TransactionList transactions={txs} />
</ThemeProvider>

// 组件内直接消费
const TransactionItem = ({ tx }) => {
  const isLarge = tx.value > 100;
  return (
    <div css={{ 
      color: isLarge ? 'red' : 'inherit',
      backgroundColor: theme.bgCard 
    }}>
      {tx.hash}
    </div>
  );
};

要是用传统 CSS,估计得写一堆 data-large="true" + [data-large="true"] { color: red; },维护起来绝对头秃。


我的建议:别站队,看场景

经过这半年的折腾,我对样式方案的看法变了:没有银弹,只有权衡

什么情况下坚持传统 CSS?

  • 项目是内容型网站(博客、文档站)
  • 团队有专职 UI 工程师,习惯用 Figma + CSS workflow
  • 极致性能要求(比如首屏速度 < 1s)
  • 需要支持老旧浏览器(IE11 及以下)

什么情况下上 CSS-in-JS?

  • 组件库开发(强隔离刚需)
  • 需要运行时动态主题/皮肤
  • 项目重度依赖 React/Vue,追求逻辑与样式统一
  • 团队愿意接受 slightly larger bundle(一般多 10~30KB)

顺便吐槽一句:有些教程一上来就说“CSS-in-JS 是未来”,结果自己 demo 里连 SSR 都没处理好,hydration 报错满天飞。技术选型不是追新,而是解决问题


最后:关于跳槽和技术债

写这篇文章的时候,我正在整理简历。三年多来,从抗拒工程化到拥抱现代化方案,踩过的坑比我写的 if-else 还多。CSS-in-JS 并不是终点——最近团队已经在调研 Vanilla Extract 这种编译时方案,试图兼顾零 runtime 和类型安全。

技术中台的日子,就是在“快速交付”和“长期可维护”之间走钢丝。每次大促前,测试同学都会幽幽地说:“这次样式别又崩了吧?” 而我能做的,就是在 deadline 前多喝几杯咖啡,把 z-index 调到 999999(开玩笑的,千万别学)。

如果你也在纠结样式方案,不妨问问自己:我的痛点到底是什么? 是团队协作混乱?性能瓶颈?还是产品经理又提了“动态换肤”这种需求?

搞清楚这点,答案自然就出来了。

P.S. 如果你对区块链前端开发感兴趣,我整理了一份入门教程清单(含 Web3.js + Ethers.js 实战),评论区留言“链上样式”我私你。反正快离职了,资料不留着发霉 :)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝