CSS-in-JS 和传统 CSS,我该选谁?

分支开太多了
2026-01-14 17:46
阅读 545

上周五晚上九点半,我家猫正趴在我键盘上打呼噜,而我在 VSCode 里疯狂切换标签页,试图搞清楚为什么这个按钮在 Safari 上莫名其妙地多了一像素边框。这已经是我本周第三次被样式问题卡住进度了——产品经理那边 deadline 明天中午就要看 demo,测试同事已经在群里@我三次问“前端联调好了没?”。

作为刚入职这家远程办公公司的试用期新人,我可不敢在这节骨眼上掉链子。毕竟转正答辩就在下个月,要是连个基础组件库都整不明白,怕是要提前开启“金三银四”求职模式了。

事情的起因其实很简单:团队要重构一个老后台系统,技术栈是 Springboot + React(别问为啥前后端混搭,问就是历史遗留)。领导说“这次要用现代化方案”,但关于样式管理方式,前端组内部吵翻了天——有人力推 CSS-in-JS,有人说“原生 CSS 够用了,别整那些花里胡哨的”。

于是,我这个“新鲜血液”被拉去调研两种方案的优劣。行吧,反正我也对前端动画和交互挺感兴趣,正好借机深入研究一下。


起手式:先搞懂它们到底是什么

很多人一听到 CSS-in-JS 就觉得是“把 CSS 写进 JS 文件里”,其实没那么简单。它本质上是一套运行时动态生成样式并注入页面的机制,最流行的库比如 styled-componentsEmotion 都属于这一类。

传统 CSS 并不单指 .css 文件,还包括 SCSS、Less 这些预处理器,以及 CSS Modules 这种局部作用域方案。咱们今天主要对比的是“全局 CSS / SCSS” vs “主流 CSS-in-JS 库”。

📌 一个小插曲:我们项目早期用的是纯 SCSS,结果某次合并代码后,两个同事写的 .button 类名冲突,导致整个表单按钮变紫色(UI 设计稿里根本没有紫色!)。运维大哥看到线上报警直接在群里发了个“???”表情包。


动手实测:从零搭建两个小 Demo

为了公平起见,我分别用两种方案实现了一个带 hover 动画的卡片组件。环境如下:

  • React 18
  • Vite 构建(别提 Webpack 了,那玩意儿启动慢得像我早上起床)
  • 浏览器:Chrome 最新版 + Safari 15(兼容性必须测!)

方案一:传统 SCSS(配合 CSS Modules)

// Card.module.scss
.card {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  transition: transform 0.3s ease, box-shadow 0.3s ease;

  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.15);
  }

  .title {
    font-size: 18px;
    font-weight: 600;
    margin-bottom: 8px;
  }
}
// Card.jsx
import styles from './Card.module.scss';

export default function Card({ title, children }) {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>{title}</h3>
      {children}
    </div>
  );
}

优点很明显:

  • 熟悉,写起来飞快
  • 支持所有 CSS 特性(包括 @keyframes 动画)
  • 构建时处理,无运行时开销
  • GitHub 上搜 .scss 文件,一眼就能看出结构

但缺点也扎心:

  • 即使用了 CSS Modules,深层嵌套的类名还是可能冲突(尤其第三方组件混用时)
  • 动态主题切换得靠 CSS 变量 or 重写 class,不够灵活
  • 想根据 props 改样式?要么写一堆 modifier class,要么用内联 style(丑哭)

方案二:CSS-in-JS(用 Emotion)

// Card.jsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

const cardStyle = (theme) => css`
  background: ${theme.bg};
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  transition: transform 0.3s ease, box-shadow 0.3s ease;

  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.15);
  }

  .title {
    font-size: 18px;
    font-weight: 600;
    margin-bottom: 8px;
  }
`;

export default function Card({ title, children, theme = { bg: 'white' } }) {
  return (
    <div css={cardStyle(theme)}>
      <h3 className="title">{title}</h3>
      {children}
    </div>
  );
}

乍一看代码量差不多,但注意几个细节:

  • 样式直接接收 JS 变量(比如 theme.bg),动态性拉满
  • 不需要单独维护 .scss 文件,组件自包含
  • 自动处理 vendor prefix(再也不用手动加 -webkit- 了!)

不过坑也不少:

  • 初次加载会多一次 JS 执行时间(虽然微乎其微)
  • SSR 场景下如果配置不对,会出现 FOUC(Flash of Unstyled Content)
  • 在 Safari 上调试时,开发者工具里看到的是一堆 hash 类名(比如 css-1a2b3c),想手动改样式调试?难!

💡 调试技巧:Emotion 提供了 label 选项,可以给生成的 class 加注释,比如 css({ label: 'Card' }),这样 DevTools 里就能看到 css-1a2b3c-Card,友好太多。


性能与兼容性:真刀真枪干一场

我把两个版本部署到测试环境,用 Lighthouse 跑了分,结果出乎意料:

指标 SCSS + Modules Emotion (CSS-in-JS)
FCP (First Contentful Paint) 1.2s 1.3s
TTI (Time to Interactive) 2.1s 2.3s
Bundle Size (gzip) +8KB +12KB
Safari 兼容性 ✅ 完美 ⚠️ 需 polyfill

结论很现实:CSS-in-JS 有轻微性能损耗,但在现代设备上几乎不可感知。真正的问题在于——我们项目要支持 iOS 12 的老设备(别问,问就是客户要求),而 Emotion 的某些特性在低版本 Safari 上会崩。

这时候就得权衡了:如果你的用户都在 Chrome/Firefox/新 Safari,CSS-in-JS 完全 OK;但如果要照顾老旧浏览器,传统 CSS 更稳妥。


团队协作 & 工程化:这才是大头!

技术再酷,落地不了也是白搭。我们团队 6 个人,3 个前端,2 个刚毕业的实习生,1 个我这样的试用期“菜鸟”。大家对 CSS-in-JS 的接受度差异很大:

  • 资深大佬:“JS 里写样式?污染逻辑,坚决不用!”
  • 实习生:“哇!这个好炫,还能用模板字符串!”
  • 我:“……能不能统一一下规范?”

于是我们做了个折中方案:

核心 UI 组件库(Button, Modal, Table)用 SCSS + CSS Modules
→ 稳定、易维护、新人上手快

业务页面中的动态/交互强的部分用 Emotion
→ 比如数据可视化图表 hover 效果、主题切换模块

顺便提一句,我们把组件库发布到了公司私有的 GitHub Packages,这样 Springboot 后台的同学也能通过 npm 引用前端组件(他们用 Thymeleaf 渲染部分页面,别笑,真的有这种混合架构)。


我的建议:别站队,按需选择

经过这两周折腾,我悟了:没有银弹,只有合适场景

场景 推荐方案
快速原型 / 内部工具 CSS-in-JS(开发效率高)
对 SEO/SSR 要求高的应用 传统 CSS(避免 FOUC)
需要深度定制主题/动态样式 CSS-in-JS(JS 变量天然支持)
团队新人多、追求稳定性 SCSS + CSS Modules
微前端架构、独立部署子应用 CSS-in-JS(避免样式污染)

另外,如果你正在学前端,我建议两种都掌握。我在准备跳槽时刷面经,发现大厂面试官特别爱问:“你怎么看待 CSS-in-JS?”——他们不是要你站队,而是看你是否理解工程权衡。


结语:试用期生存指南

写这篇文章的时候,我的转正 PPT 还没做完。但至少,这次样式方案之争让我在团队里露了脸——上周站会上,我展示了对比数据和 demo,leader 点头说“思路很清晰”。

远程办公最大的挑战不是技术,而是如何让别人看到你的价值。所以,哪怕是个试用期小透明,也要敢输出、敢分享。

对了,如果你也在用 Springboot + React 做全栈,或者正在纠结样式方案,欢迎来我 GitHub 仓库提 issue(链接就不放了,免得像打广告)。说不定哪天,我们还能一起写个《Springboot 前端集成避坑教程》呢 😄

最后,祝各位 coder 少点 bug,多点下班时间。毕竟,猫还在等我关电脑呢。

评论 0

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