CSS-in-JS真香?别急,先看看我们双非学生的血泪实战
去年11月,我还在为一个内部运营系统的重构焦头烂额。那是我在学校开源社团“码农自救联盟”干的第23个月——没错,快两年了。作为一所双非院校的大二学生,我没进过大厂,没拿过顶会论文,但靠着死磕开源项目源码和周末泡在GitHub上,居然也在团队里混成了“样式负责人”(其实就是没人愿意碰CSS,全甩给我了)。
那天晚上11点,产品经理突然在钉钉群里@我:“首页加载卡成PPT了,用户反馈说按钮点了没反应,明天早上8点前必须修好,不然双11运营活动没法上线!”我盯着Chrome DevTools里那坨300KB未压缩的CSS文件,心里一万只草泥马奔腾而过。更糟的是,团队里有人提议:“要不试试styled-components?听说大厂都在用CSS-in-JS,贼高级。”
我差点一口老血喷出来。CSS-in-JS?听起来很酷,但我们这个小破项目连TypeScript都没上全,还搞什么黑科技?但转念一想,既然都快秃了,不如趁机把这事彻底搞明白:到底该用传统的CSS/SCSS,还是拥抱CSS-in-JS?
一场关于“样式归属权”的战争
说实话,刚接触前端时,我也觉得CSS是门玄学。.container .btn:hover { color: red !important; } 这种写法能跑就行,哪管什么架构。但随着项目变大、协作人数增加,问题就来了。
我们组五个人,三个后端转前端(包括我自己),两个设计转开发。每次改个按钮样式,都要小心翼翼地检查会不会影响其他页面。最经典的一次事故:小李为了改登录页的输入框,不小心覆盖了全局的.input类,结果整个后台系统的表单全崩了。测试同学直接在群里发了个“???”,运维大哥差点没把我从服务器上踢下来。
这时候,作用域隔离就成了刚需。传统CSS靠命名规范(比如BEM)来避免冲突,但人总会犯错。而CSS-in-JS的核心卖点之一,就是自动作用域——每个组件的样式只属于它自己,天然隔离。
// styled-components 示例
import styled from 'styled-components';
const PrimaryButton = styled.button`
background: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
&:hover {
background: #0056b3;
}
`;
// 这个样式只会应用到 PrimaryButton,不会污染全局
看起来很美好,对吧?但现实没那么简单。
性能:别被“运行时生成”忽悠了
我一开始也被CSS-in-JS的“动态主题”、“props传参”这些特性迷住了。比如根据用户偏好切换暗色模式:
const ThemeButton = styled.button`
background: ${props => props.theme === 'dark' ? '#333' : '#fff'};
`;
但很快我就发现不对劲。本地开发时还好,一打包上线,Lighthouse评分直接掉到60分。打开Performance面板一看,首屏渲染时间多了近400ms!原因很简单:CSS-in-JS是在JavaScript执行时动态生成<style>标签的,这意味着浏览器得等JS解析完才能知道样式长啥样,严重阻塞渲染。
而传统CSS呢?只要放在<head>里,浏览器可以并行下载和解析,甚至能利用HTTP/2多路复用提前加载。
更惨的是缓存。我们的运营系统每周都要推新活动,JS bundle经常变,导致包含样式的JS文件无法长期缓存。而单独的.css文件,只要内容不变,CDN就能缓一年。
| 方案 | 首屏FCP (ms) | 样式缓存能力 | 动态主题支持 | 学习成本 |
|---|---|---|---|---|
| 传统CSS + BEM | ~800 | ⭐⭐⭐⭐⭐ | 需配合CSS变量 | ⭐ |
| SCSS + CSS Modules | ~850 | ⭐⭐⭐⭐ | 中等 | ⭐⭐ |
| styled-components | ~1200 | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| Emotion (with SSR) | ~950 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
注:数据基于我们内部运营系统实测,环境:React 18 + Webpack 5,模拟3G网络
开发体验:真香 vs 真坑
作为每天和代码谈恋爱的人,开发体验当然重要。CSS-in-JS最大的爽点就是不用切文件。以前写组件要开三个窗口:.js、.scss、.test.js,现在全在一个文件里搞定,IDE还能智能提示。
而且它天然支持JavaScript的所有能力。比如我想根据屏幕宽度动态调整padding:
const ResponsiveCard = styled.div`
padding: ${props => props.isMobile ? '8px' : '16px'};
`;
传统CSS就得写媒体查询,或者用JS计算后设className,啰嗦得很。
但坑也多。最让我抓狂的是调试困难。DevTools里看到的class名是自动生成的,像sc-gsnTZj kFQwVd这种,根本不知道对应哪个组件。虽然styled-components提供了displayName插件,但配置起来又是一堆webpack loader。
还有TypeScript支持。虽然现在主流CSS-in-JS库都支持TS,但类型推导经常抽风。有次我传了个color={user.themeColor},结果themeColor是string | undefined,运行时直接报错,而SCSS里就算颜色值错了,顶多显示不出来,不至于白屏。
团队协作与“综合”考量
说到这儿,不得不提“综合”这个词。技术选型从来不是看哪个更酷,而是看团队、项目、运营节奏的综合匹配度。
我们是个学生团队,人员流动大(每届毕业就走一批),项目又多是短期运营活动(比如校园招聘季、社团招新),生命周期短,迭代快。这种情况下,稳定性 > 新潮性。
有一次,我强行在新活动中上了styled-components,结果学弟接手时一脸懵:“哥,这button的样式在哪改?”他翻了半天代码,才发现样式藏在JS里。而如果用传统CSS,他只要搜.activity-btn就能定位。
另外,我们的“运营”同学(其实就是社团宣传部)经常会临时改需求。比如上周五晚上9点,他们突然说:“主色调要从蓝色改成绿色,明天早上升旗仪式要用!” 如果用CSS变量+传统SCSS,我改一行$primary-color: green;就行。但如果是CSS-in-JS,就得遍历所有组件找硬编码的颜色值——除非你一开始就设计好主题系统,可谁有那闲工夫?
我们的“混合策略”:既要又要还要
折腾半年后,我终于悟了:没有银弹,只有权衡。
现在我们采用一种“混合策略”:
- 基础组件库(Button, Modal, Form):用SCSS + CSS Modules。理由:稳定、可复用、易调试。
- 动态主题/复杂交互组件(比如数据可视化卡片):用Emotion(比styled-components性能好点,支持SSR提取静态CSS)。
- 一次性运营活动页:纯内联style + 少量全局CSS。理由:快!反正下周就下线。
具体怎么配?举个Webpack例子:
// webpack.config.js
module.exports = {
module: {
rules: [
// CSS Modules for components
{
test: /\.module\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { modules: true }
},
'sass-loader'
]
},
// Global CSS for resets and utilities
{
test: /^(?!.*\.module).*\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
}
};
同时,在package.json里明确规范:
{
"scripts": {
"lint:css": "stylelint \"src/**/*.scss\""
},
"stylelint": {
"extends": "stylelint-config-standard-scss",
"rules": {
"selector-class-pattern": "^[a-z][a-zA-Z0-9]*$"
}
}
}
这样,新人来了也知道什么该放哪,减少沟通成本。
给同样“双非自学”的兄弟姐妹们一点建议
如果你和我一样,来自普通院校,靠自学闯荡前端江湖,我的建议是:
- 别盲目追新。CSS-in-JS不是洪水猛兽,也不是救命稻草。先搞懂传统CSS的盒模型、层叠上下文、BFC这些底层原理,再谈“超越”。
- 从痛点出发。如果你的项目只有几个页面,用原生CSS + 命名规范完全够用。只有当样式冲突频繁、动态需求多时,才考虑CSS-in-JS。
- 重视可维护性。学生项目往往没人review代码,所以越简单越不容易出错。一个清晰的SCSS文件结构,比炫技的JS样式更珍贵。
- 学会“妥协”。技术选型是艺术,不是科学。有时候为了赶deadline,用最熟的方案反而是最优解——毕竟,活着才能继续coding。
上周,我又一次熬到凌晨改样式。但这次,我没再纠结用哪种方案。打开编辑器,新建了个ActivityBanner.module.scss,三下五除二搞定。提交代码,关电脑,回宿舍的路上抬头看了眼月亮——嗯,还是CSS最靠谱。
毕竟,在这个充满不确定性的世界里,至少.container { display: flex; }永远值得信赖。

评论 0