CSS-in-JS 还是传统 CSS?我在跳槽前的技术纠结
上周五晚上九点半,我瘫在工位上,耳机里循环着 Lofi Hip Hop,盯着屏幕上一个 React 组件的样式冲突问题,突然意识到:这破项目已经用了三种不同的样式方案——全局 SCSS、CSS Modules,还有不知道哪个实习生引入的 styled-components。产品经理还在群里@我说“明天上线前要把那个 hover 动效改得更丝滑一点”,而我连到底该在哪改都搞不清楚。
坐标北京,每天通勤一小时,最近又在考虑要不要跳槽。不是因为受不了加班(虽然双11期间连续一周凌晨回家确实有点顶),而是觉得技术栈太乱了,想找个方向清晰点的团队。但跳之前,总得先搞明白现在前端圈里吵得最凶的问题之一:CSS-in-JS 到底香不香?传统 CSS 是不是该被淘汰了?
这篇文章,就是我在整理个人知识体系、准备面试时的真实思考。写给和我一样在技术选型中挣扎的前端同行。
起因:一个“简单”的需求引发的血案
事情要从两周前说起。我们团队接了个新需求:做一个数据爬虫结果的可视化看板。产品经理甩过来一张 Figma 设计图,要求卡片 hover 时有缩放+阴影+背景渐变的复合动画,点击后还能展开详情。听起来很常规对吧?但问题来了——这个组件要嵌入到公司多个老系统中,有些还是 jQuery 写的,有些用的是 Vue 2,而我们的新项目是 React + TypeScript。
为了复用,我决定把它做成一个独立的 React 组件包。但样式怎么处理?如果用传统全局 CSS,class 命名冲突几乎是必然的(上次就因为 .card 和老系统的 .card 冲突,导致整个首页布局崩掉,运维差点把我拉黑)。但如果用 CSS-in-JS,会不会增加 bundle size?性能会不会炸?尤其是这个看板要展示几百个卡片,每个都有复杂交互。
我翻遍了 GitHub 上 star 最高的几个方案:styled-components、Emotion、Linaria,还有人安利我直接用 vanilla-extract。越看越懵,干脆自己动手试一遍。
实战对比:三套方案跑同一个动画组件
我基于同一个设计稿,分别用三种方式实现了这个卡片组件:
- 传统 CSS + CSS Modules(算是“传统”里的现代化方案)
- styled-components(CSS-in-JS 的代表)
- Emotion + css prop(据说性能更好)
方案一:CSS Modules —— 熟悉的配方,熟悉的安心感
/* Card.module.css */
.card {
transition: all 0.3s ease;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card:hover {
transform: scale(1.03);
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
}
import styles from './Card.module.css';
const Card = () => (
<div className={styles.card}>...</div>
);
优点:
- 零运行时开销,样式编译成静态 CSS 文件
- 浏览器原生支持,动画性能稳如老狗
- 我这种老古董写起来顺手,调试时直接在 DevTools 里改就行
痛点:
- 动态主题切换?得靠 CSS 变量或者 JS 操作 class,不够优雅
- 嵌套选择器写多了容易失控(比如
&:hover &__title这种) - 和 React 的状态逻辑割裂——比如“加载中”状态要改样式,得额外加个 class
方案二:styled-components —— 写 JSX 的快乐谁懂
import styled from 'styled-components';
const StyledCard = styled.div`
transition: all 0.3s ease;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
&:hover {
transform: scale(1.03);
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
}
`;
const Card = () => <StyledCard>...</StyledCard>;
爽点:
- 样式和组件逻辑完全融合!比如可以轻松根据 props 改颜色:
const StyledCard = styled.div<{ $loading?: boolean }>` opacity: ${props => props.$loading ? 0.6 : 1}; `; - 自动防冲突,每个组件生成唯一 class 名(虽然 DevTools 里看着像乱码)
- 支持 ThemeProvider,换肤功能一行代码搞定
坑点:
- 初次渲染会动态插入
<style>标签,首屏可能闪一下(SSR 能缓解但配置麻烦) - bundle size 增加约 12KB(gzip 后),对我们这种追求极致性能的项目有点肉疼
- 动画性能?实测在低端安卓机上 hover 卡顿明显,因为 transform 触发了重排(后来发现是没加
will-change: transform)
方案三:Emotion —— CSS-in-JS 的性能优等生
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const cardStyle = css`
transition: all 0.3s ease;
border-radius: 8px;
&:hover {
transform: scale(1.03);
/* ...其他样式 */
}
`;
const Card = () => <div css={cardStyle}>...</div>;
亮点:
- 编译时提取静态样式(配合 Babel 插件),运行时开销比 styled-components 小
- 支持
cssprop,不用包裹一层组件,JSX 更干净 - 对服务端渲染友好,默认就支持
暗坑:
- 配置 Babel 插件时踩了大雷——忘记加
sourceMap: true,调试时定位不到原始代码行 - 和某些旧版 Webpack 冲突,导致 HMR 失效(最后升级了 loader 才解决)
- 团队新人看到
cssprop 一脸懵:“这玩意是 React 官方的吗?”
性能实测:别光听厂商吹,数据说话
我在 MacBook Pro M1 上用 Chrome DevTools 的 Performance 面板做了三组测试:渲染 200 个卡片,连续 hover 10 秒。
| 方案 | 首屏时间 (ms) | FPS (hover 时) | Bundle Size 增量 |
|---|---|---|---|
| CSS Modules | 120 | 58 | +0 KB |
| styled-components | 185 | 42 | +12 KB |
| Emotion (with Babel) | 140 | 52 | +8 KB |
结论很直观:
- 如果你的项目对首屏速度极度敏感(比如信息流、电商首页),传统 CSS 仍是王者
- 如果交互复杂、需要深度结合状态(比如管理后台、可视化工具),CSS-in-JS 的开发体验优势碾压性能损失
- Emotion 在两者之间取得了不错的平衡,尤其适合新项目
特别提一嘴:所有方案在开启 will-change: transform 后,FPS 都提升到了 55+。所以动画性能瓶颈往往不在方案本身,而在你有没有用对 CSS 属性。
真实项目中的决策建议
经过这次折腾,我对不同场景有了更清晰的判断:
✅ 优先用 CSS Modules / Sass 的情况:
- 公司有严格的 bundle size 限制(比如我们爬虫看板要嵌入第三方页面,不能超 50KB)
- 团队里有非前端成员要改样式(设计师用 Figma 插件直接导出 CSS)
- 项目生命周期短,不想引入额外依赖(比如临时活动页)
✅ 优先用 CSS-in-JS(Emotion 为主)的情况:
- 组件库开发(隔离性太重要了)
- 需要动态主题/夜间模式
- 复杂交互动效(比如拖拽、手势反馈),样式需随 state 实时变化
⚠️ 谨慎使用的情况:
- 项目已重度使用全局 CSS,强行引入 CSS-in-JS 会导致维护成本飙升
- 团队对新技术接受度低(别问我怎么知道的,上次推 Tailwind 被后端同事说“花里胡哨”)
关于跳槽的额外思考
写到这里,我突然明白为什么面试官总问“你们项目的样式方案”。这其实是在考察你的工程权衡能力——能不能根据业务场景、团队规模、性能要求做取舍,而不是盲目追新。
我现在看新公司的 JD,会特意留意他们用的样式方案。如果写着“熟练使用 styled-components”或“有 CSS-in-JS 优化经验”,大概率是重视前端工程化的团队;如果还停留在 “熟悉 Bootstrap”,那可能技术债比较重……(当然也可能人家业务简单,没必要上复杂方案)
不过话说回来,技术只是工具。上周我修好那个样式冲突 bug 后,产品居然请我喝了杯瑞幸——原来他根本分不清 CSS Modules 和 styled-components 的区别,他只在乎“动效丝滑”。想到这儿,我又把耳机音量调大了一点,继续敲代码。毕竟,下一份 offer 还在路上呢。
附:我的最终选择
对于那个爬虫可视化看板,我折中了一下:
- 核心布局和基础样式用 CSS Modules(保证轻量和兼容)
- 复杂交互部分(hover 动画、加载状态)用 Emotion 的
cssprop
这样既控制了体积,又保留了动态能力。上线后 PM 再也没找过我改样式——可能他终于学会用 Figma 了?(狗头保命)
如果你也在纠结这个问题,不妨问问自己:你的用户更在意加载速度,还是交互体验?你的团队更怕 bundle size,还是 class 冲突?
答案不同,选择自然不同。技术没有银弹,只有最适合当下场景的解法。
(完)
P.S. 写完这篇文章,我关掉电脑看了眼招聘软件——有家公司要求“精通 CSS-in-JS 与传统 CSS 的混合架构”,嗯,这岗位有点意思……

评论 0