聊聊我为什么从 CSS-in-JS 又回到了传统 CSS
一个写了 6 年 iOS 的老兵,最近在前端样式方案上反复横跳的心路历程
引子
先说下我自己吧。坐标成都,做 iOS 开发快六年了,从 Objective-C 一路写到 Swift,见证了 Swift 从被吐槽到真香的整个过程。平时写代码喜欢戴个耳机,网易云日推循环播放,成都这地方生活节奏慢,下班后去玉林路喝个小酒,日子过得还算巴适。
最近公司搞全栈化,我被迫(好吧,其实也有点自愿)开始写前端。本来想着 iOS 都玩得转,前端能有多难?结果被 CSS 教做人了。更离谱的是,我在研究样式方案的时候,顺手翻了翻组里新来的应届生的简历,好家伙,CSS-in-JS 写得飞起,传统 CSS 反而一问三不知。这让我开始认真思考:现代前端开发,样式方案到底该怎么选?
这篇文章就当是我的开发心得记录吧,希望能给同样在纠结的朋友一点参考。
事情是这样的
上个月,我们组接了个内部管理系统重构的项目。技术栈选了 React + TypeScript,这个没争议。但到了样式方案选型的时候,团队里吵翻了天。
前端组长小王(98年的,卷王本王)力推 Styled Components,说这是现代前端的标配,组件化开发就该用 CSS-in-JS。老李(跟我一样是从后端转过来的)则坚持用传统 CSS Modules,说 CSS-in-JS 性能有坑,调试能把你搞疯。
我当时的反应是:你们 iOS 开发选样式方案从来不用这么纠结啊……UIKit 一套搞定,SwiftUI 又一套,哪来这么多花样。
但前端就是这么魔幻。
先说说 CSS-in-JS 那些事
真香时刻
刚开始用 Styled Components 的时候,我是真觉得爽。你看这段代码:
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
opacity: 0.85;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
`;
// 使用
<Button primary>提交</Button>
<Button>取消</Button>
写 iOS 的时候我就经常吐槽 UIKit 的样式设置太分散,一个按钮的背景色、圆角、字体要写好几行。CSS-in-JS 这种"样式即组件"的感觉,确实很对胃口。
而且它解决了几个痛点:
- 命名冲突:自动生成唯一类名,再也不用担心
.btn被全局污染 - 动态样式:props 直接传进去,比写一堆
className={styles.active}清爽多了 - 死代码消除:组件删了,样式自动没了,不用手动清理
翻车现场
但好景不长。
项目上线两周后,测试小姐姐跑来找我:"这个页面加载怎么这么慢?"
我打开 Chrome DevTools 一看,好家伙,光 CSS 注入就花了 800ms。Styled Components 在运行时要动态生成 <style> 标签并注入 DOM,组件一多,这个过程就成了性能瓶颈。
更头疼的是调试。传统 CSS 在 DevTools 里一目了然,CSS-in-JS 生成的类名长这样:
.sc-bdVaJa.gHkLmN {
background: #007bff;
}
找个样式跟破案似的。上周五晚上加班改一个线上样式 Bug,我对着屏幕找了半小时才定位到是哪个组件的样式,当时真的想砸键盘。
还有个坑是 SSR。我们有个页面需要做服务端渲染,Styled Components 需要额外配置 ServerStyleSheet,而且如果忘了处理,客户端 hydrate 的时候样式会闪烁。我第一版上线就踩了这个坑,被领导在周会上点名批评(虽然语气很温和,但我脸皮薄啊)。
传统 CSS 的反击
被 CSS-in-JS 搞得心力交瘁后,我开始认真看传统 CSS 方案。
CSS Modules:低调的实力派
/* Button.module.css */
.button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.button:hover {
opacity: 0.85;
transform: translateY(-1px);
}
.primary {
background: #007bff;
color: white;
}
.secondary {
background: #6c757d;
color: white;
}
import styles from './Button.module.css';
function Button({ primary, children }) {
const className = `${styles.button} ${primary ? styles.primary : styles.secondary}`;
return <button className={className}>{children}</button>;
}
没有运行时开销,样式在构建时就确定了。DevTools 里看到的类名虽然也做了 hash(Button_button__x7k2m),但至少你能从文件名看出来源。
性能对比数据(我们项目实测):
| 方案 | 首屏 CSS 加载耗时 | 运行时开销 | 包体积影响 |
|---|---|---|---|
| Styled Components | ~800ms | 有 | +12KB gzipped |
| CSS Modules | ~50ms | 无 | 0 |
| 传统 CSS | ~50ms | 无 | 0 |
这差距,在移动端弱网环境下就是"秒开"和"转圈圈"的区别。
但传统 CSS 也有它的痛
说传统 CSS 完美那是骗人的。
最大的问题是动态样式。比如主题切换、根据用户配置动态调整颜色,CSS Modules 处理起来就很别扭:
// 这种写法你受得了吗?
<div
className={styles.container}
style={{
'--primary-color': theme.primaryColor,
'--border-radius': theme.borderRadius
}}
>
每次都要在 JSX 里写 style 对象,跟 CSS-in-JS 的优雅比起来,确实土了点。
还有个问题是设计系统的维护。我们有一套组件库,用 CSS Modules 写的时候,主题变量要全局定义,组件之间的样式依赖关系不直观。后来我花了两个周末重构,才把变量体系理顺。
我的最终选择
折腾了一大圈,我的结论是:没有银弹,看场景选方案。
具体来说:
用 CSS Modules 的场景:
- 中后台管理系统(样式相对固定,性能敏感)
- 移动端 H5(首屏速度是生命线)
- 团队里有新手(学习成本低,调试友好)
用 CSS-in-JS 的场景:
- 组件库开发(需要高度动态化的样式)
- C 端产品且对性能不敏感(比如内部工具)
- 团队技术栈统一,有完善的 lint 和 review 机制
我现在的实践是混合方案:
基础组件和页面布局用 CSS Modules,保证性能和可维护性。少数需要高度动态化的场景(比如用户自定义主题、数据可视化图表的样式)用 Emotion(比 Styled Components 轻量,API 更灵活)。
// 基础布局 - CSS Modules
import styles from './Layout.module.css';
// 动态主题 - Emotion
import { css } from '@emotion/react';
const dynamicTheme = css`
--bg-color: ${userConfig.bgColor};
--text-color: ${userConfig.textColor};
`;
一些掏心窝子的话
写到这里,突然有点感慨。
做 iOS 这些年,Swift 刚出来的时候也是一堆人吹,说是要取代 ObjC。结果呢?到现在很多老项目还是 ObjC 为主,Swift 和 ObjC 混编才是常态。技术选型也是这样,没有绝对的好坏,只有适不适合。
前端这个领域变化太快了,去年还在吹 CSS-in-JS,今年 Tailwind CSS 又成了新宠。我有时候看着各种技术文章,也会焦虑:是不是又落后了?
但后来想通了。技术是为人服务的,能解决问题、能让团队高效协作、能让用户用得爽,就是好方案。不用盲目追新,也不用固守旧法。
对了,最近闲下来在研究 Rust,感觉挺有意思的。所有权机制、生命周期这些概念,跟 Swift 的内存管理有异曲同工之妙。等搞明白了,再来写篇文章聊聊。
写在最后
如果你也在纠结样式方案,我的建议是:
- 先想清楚你的场景:是 C 端还是 B 端?性能要求高不高?团队水平怎么样?
- 小范围试水:别一上来就全量切换,先在一个模块里试试,踩踩坑
- 建立规范:不管选哪种,都要有统一的代码规范和 review 机制
- 关注性能:上线前一定要做性能测试,别等用户投诉了才发现问题
好了,就写到这儿吧。楼下玉林路的酒馆要开门了,我去整两杯。成都的夜生活,才刚刚开始呢。
如果你也有样式方案选型的经验或踩坑经历,欢迎在评论区交流。毕竟,程序员的快乐,就是互相倒苦水嘛。


评论 0