一场样式方案的“路线之争”:我在成都写CSS的血泪史

技术森林
2026-01-13 05:46
阅读 795

上周五晚上十点半,我还在公司调一个诡异的样式优先级问题。产品那边催着周一上线新活动页,测试同学已经发来第三轮 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>
);

关键升级点:

  1. CSS Modules:自动加 hash,实现局部作用域,避免命名冲突。
  2. Design Token:用 PostCSS 插件把 --color-primary 编译成具体值,保证设计一致性。
  3. 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

最热最新
暂无评论
技术森林Lv.1
0
影响力
0
文章
0
粉丝