一场样式方案的“路线之争”:我在成都写CSS的血泪史
上周五晚上十点半,我还在公司调一个诡异的样式优先级问题。产品那边催着周一上线新活动页,测试同学已经发来第三轮 bug 清单,而我的 MacBook Pro 风扇呼呼作响,仿佛在替我呐喊:“这破 CSS 到底谁覆盖了谁?!”
我是成都某上市公司的技术中台工程师,日常负责搭建和维护公司前端基础架构。我们团队不大,但节奏很舒服——至少理论上是这样。现实是,每逢大促(比如双11、618),整个中台就得顶上去支援业务线,而样式系统,永远是第一个背锅的。
去年我们开始重构主站,领导拍板:“这次要用现代方案,别再搞一堆全局 class 满天飞了。”于是,CSS-in-JS 和传统 CSS 的“路线之争”正式在我司上演。今天不讲大道理,就说说我踩过的坑、掉过的头发,以及最后怎么说服后端同事不再嘲笑我们“连个颜色都管不好”。
为什么连样式都要“架构”?
先说背景。我们主站用的是 React + TypeScript 技术栈,组件库已经迭代到第三代。早期为了赶进度,大家直接在 .css 文件里写样式,结果就是:
- 同一个按钮,在首页叫
.btn-primary,在订单页叫.submit-btn - 全局变量
--main-color被五个团队改了七次,每次发布都像开盲盒 - 后端同事偶尔帮我们修个 UI,一不小心把
padding: 10px写成padding: 10,然后 QA 群炸了
更惨的是,有一次我们引入了一个第三方 React 组件库,它自带一套 CSS,结果和我们的全局 reset 冲突,导致整个支付页的字体变小了。用户投诉截图里那行“请确认金额”的文字几乎看不见——那一刻,我真的想砸电脑。
所以,当新项目启动时,我们决定彻底解决样式混乱问题。摆在面前的两条路:拥抱 CSS-in-JS(比如 styled-components 或 Emotion),或者 用现代工具链升级传统 CSS(如 CSS Modules + PostCSS)。
CSS-in-JS:真香还是真坑?
一开始,我和几个前端小伙伴是 CSS-in-JS 的铁粉。毕竟,它解决了我们最痛的点:作用域隔离。
// 用 Emotion 写个按钮,样式天然 scoped
import { css } from '@emotion/react';
const PrimaryButton = ({ children }) => (
<button
css={css`
background: #007AFF;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
&:hover {
background: #0056b3;
}
`}
>
{children}
</button>
);
看,多干净!不用想 class 名,不用怕冲突,甚至还能用 JS 变量动态控制样式(比如根据主题色切换)。而且配合 React 的 props,逻辑和样式紧耦合,符合“组件即一切”的哲学。
但很快,问题来了。
坑1:性能不是免费的
CSS-in-JS 在运行时会动态生成 <style> 标签。页面组件一多,DOM 里塞满内联样式,首屏渲染变慢。我们压测发现,一个包含 200+ 组件的页面,styled-components 会导致 FCP(First Contentful Paint)增加 300ms+。
更糟的是 SSR(服务端渲染)。虽然 Emotion 支持 extractCritical,但配置复杂,稍不注意就会出现“客户端水合时样式闪烁”。有次上线后,用户反馈页面“闪一下白屏”,查了半天才发现是 CSS-in-JS 的 hydration 没对齐。
坑2:调试体验反人类
Chrome DevTools 里看到的 class 名是 css-1x9zq3a 这种 hash,根本没法直观对应到代码。虽然可以配 babel-plugin-emotion 加 source map,但实际效果一般。相比之下,传统 CSS 的 .product-card__title 至少能猜出用途。
有一次,产品经理指着设计稿说:“这个间距不对,应该是 12px,不是 16px。”我打开 DevTools,对着一串 css-xyz123 发呆了五分钟——最后靠全局搜索才定位到代码位置。
坑3:后端和设计师一脸懵
我们有个内部低代码平台,后端同事偶尔要写点简单页面。他们熟悉 HTML/CSS,但看到 JSX 里夹杂着 css 字符串,直接投降:“这玩意儿算前端还是算 JS?”
设计师用 Figma 标注样式,导出的也是标准 CSS 属性。让他们理解 “你不能直接复制这段 background-color 到 Emotion 里,因为我们要用 token” —— 整个协作流程变得别扭。
回归传统 CSS:用现代化武器武装自己
吃了一顿亏后,我们重新评估了“传统”CSS。其实,现在的 CSS 已经不是十年前那个 CSS 了。
我们最终选型:CSS Modules + PostCSS + Design Token
/* Button.module.css */
.primary {
background: var(--color-primary);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
}
.primary:hover {
background: var(--color-primary-hover);
}
// Button.jsx
import styles from './Button.module.css';
const PrimaryButton = ({ children }) => (
<button className={styles.primary}>{children}</button>
);
关键升级点:
- CSS Modules:自动加 hash,实现局部作用域,避免命名冲突。
- Design Token:用 PostCSS 插件把
--color-primary编译成具体值,保证设计一致性。 - TypeScript 支持:通过
typings.d.ts自动生成 CSS 类名的类型定义,写错 class 会报错!
这套方案保留了 CSS 的声明式优势,又解决了老 CSS 的痛点。更重要的是,调试友好——DevTools 里看到的就是 Button_primary__abc123,一眼知道来源。
实战对比:关键指标拉出来遛遛
为了说服团队,我做了个对比表格(数据来自我们真实项目):
| 维度 | CSS-in-JS (Emotion) | 传统 CSS (Modules + PostCSS) |
|---|---|---|
| 构建体积 | +15%(需打包 runtime) | 无额外 JS |
| 首屏 FCP | 1.8s | 1.4s |
| 调试体验 | ⭐⭐ | ⭐⭐⭐⭐ |
| 设计师协作 | 困难 | 顺畅 |
| 动态主题支持 | 原生支持 | 需配合 CSS Variables |
| 后端可维护性 | 几乎为零 | 较高 |
| 浏览器兼容性 | 依赖 JS,IE 基本放弃 | 原生 CSS,兼容性好 |
结论很明显:如果你追求极致性能、团队有非前端成员参与、或需要良好兼容性,传统 CSS + 现代工具链更稳。
当然,CSS-in-JS 在某些场景仍有优势,比如高度动态的主题切换、或完全由前端掌控的小型 SPA。但我们是中台,要支撑几十个业务线,稳定和协作效率比“炫技”重要得多。
最后的选择:务实主义者的胜利
现在,我们主站新项目统一采用 CSS Modules + Design Token 方案。为了提升体验,还写了内部 CLI 工具,一键生成带 token 的组件模板。连后端同事都说:“原来写个按钮这么简单。”
有趣的是,最近读《Design Systems Handbook》这本书,里面提到:“工具的选择应服务于团队,而非相反。”这句话让我释然——我们不是在选技术,而是在选工作方式。
至于 CSS-in-JS?它没死,只是不适合我们。就像我 Mac 上装了 Windows 虚拟机,平时不用,但测试 IE 兼容性时还得打开——工具而已,别上纲上线。
给同行的建议
如果你也在纠结:
- 小团队、纯前端、追求 DX → 可以试试 CSS-in-JS,但务必做好性能监控。
- 中大型团队、多角色协作、重性能与稳定 → 用现代 CSS 方案,搭配工具链升级。
- 别为了“现代化”而现代化。有时候,最朴素的方案反而最持久。
最后,分享一句我们 leader 的话:“技术选型不是选女朋友,不用找最漂亮的,要找最合适的。”
好了,我去修那个周五没搞定的样式 bug 了。希望这次,别再是 !important 搞的鬼。

评论 0