一次大型项目重构后的样式方案选择:CSS-in-JS vs 传统CSS实战经验分享

代码杂货铺
2025-06-15 13:41
阅读 218

开篇:从一个项目重构说起

开篇:从一个项目重构说起

去年年底,我参与了一个大型中后台系统的前端重构工作。这个项目原本是用jQuery + 原生HTML/CSS实现的,随着业务功能不断叠加,样式代码逐渐变得难以维护:命名冲突、层级混乱、样式污染问题频出,开发过程中常常改一个按钮颜色就要花上大半天时间排查。

项目决定迁移到 React 技术栈,其中关于样式方案的选择引发了团队内的激烈讨论。大家争论的核心问题是:继续沿用传统的 CSS/SASS 写法,还是尝试更现代的 CSS-in-JS 方案?

这篇文章就基于那次重构中的实际体验,聊聊我在不同阶段使用 CSS 和 CSS-in-JS 的感受、踩过的坑、做出选择时的关键判断点,以及最终方案落地后的真实效果反馈。希望能给正在做技术选型或面临类似纠结的你一些启发和参考。


一、问题描述:为什么我们要重新考虑样式管理方式?

一、问题描述:为什么我们要重新考虑样式管理方式?

在重构前,我们面对的问题可以用三个词概括:“难以维护”、“容易冲突”、“缺乏模块化”。

举个具体的例子:

我们有一个通用的 Button 组件,类名是 .btn,它被多个页面复用。某天产品说:“点击按钮后要显示 loading 状态。”于是一个开发兄弟加了新的样式:

.btn.is-loading {
  background: #999;
}

看起来没毛病。结果第二天 QA 测试发现,在首页某个组件里所有用了 .is-loading 的元素都变了灰——因为别的地方也有类似的类名!而这类问题在整个项目中层出不穷。

更让人头疼的是,项目初期没有统一的命名规范,不同人写的 class 名五花八门:.button, .btn, .baseBtn, .submitBtn, .loadingBtn……

这些问题总结下来,其实暴露了传统 CSS 的几个明显短板:

  • 全局作用域导致的样式冲突
  • 难以追踪依赖关系
  • 缺乏逻辑表达能力(不能写变量、函数等)
  • 样式和组件结构分离,难以直观看到对应关系

所以我们迫切需要一种能解决这些问题的新方案。


二、解决方案对比:CSS 还是 CSS-in-JS?

二、解决方案对比:CSS 还是 CSS-in-JS?

当时团队内部形成了两种主流声音:

主张继续用 CSS 的理由:

  1. 学习成本低,老成员熟悉;
  2. 可以利用 SCSS 写变量、mixins 等提升可维护性;
  3. 构建体积更小(CSS 文件比 JS 生成的 style 更小);
  4. 对 SEO 友好,服务端渲染无闪屏。

主张尝试 CSS-in-JS 的理由:

  1. 模块化强,天然防冲突;
  2. 支持条件样式、动态主题等高级特性;
  3. 样式与组件绑定更紧密,利于封装;
  4. 支持类型推断(如 with TypeScript);
  5. 现代框架生态中越来越多采用 CSS-in-JS(如 Material UI、Ant Design)。

经过多轮讨论和技术调研,我们决定尝试 CSS-in-JS,并选择了 emotion 作为主要工具库(原因后面会讲)。


三、技术选型细节:为何是 Emotion 而非 Styled Components?

三、技术选型细节:为何是 Emotion 而非 Styled Components?

在众多 CSS-in-JS 库中,最主流的两个是 Styled ComponentsEmotion。我们最终选择 Emotion 的理由主要有以下几点:

  1. 编译性能更好:在大型项目中,emotion 的构建速度明显优于 styled-components,尤其在 SSR 场景下。
  2. 支持多种用法:既可以通过 styled() 创建组件,也可以直接内联对象传给 css() 方法,灵活性更高。
  3. TypeScript 支持更友好:配合 Babel 插件可以自动推断组件 props 类型。
  4. 社区活跃度高且稳定:我们调研时 emotion 已经在多个大型开源项目中验证过稳定性。

比如下面是一个典型的 emotion 使用方式:

/** @jsx jsx */
import { css, jsx } from '@emotion/react'

const Button = ({ primary }) => (
  <button
    css={css`
      background-color: ${primary ? '#007bff' : '#ccc'};
      border: none;
      color: white;
      padding: 10px 20px;
      &:hover {
        opacity: 0.9;
      }
    `}
  >
    Click Me
  </button>
)

通过这种方式,我们可以做到:

现代网页界面设计示例-2

  • 每个组件的样式独立作用域;
  • 动态传入 props 控制样式;
  • 直接使用 JavaScript 表达式控制样式值;
  • 避免全局 class 名冲突。

四、实战踩坑记录与解决方案

虽然 CSS-in-JS 提供了更模块化的写法,但在实际项目中也遇到了一些预料之外的问题。这里分享几个印象深刻的案例。

🕳️ 坑1:样式重复 & 性能问题

刚开始大量使用 inline css 属性时,我们在一个循环组件中写了类似这样的代码:

function ListItem({ item, active }) {
  return (
    <div
      css={{
        color: active ? 'red' : 'black',
        fontSize: '16px'
      }}
    >
      {item.name}
    </div>
  )
}

这样写在视觉上没问题,但会导致每次渲染都生成新的 class name,造成重复插入 <style> 标签,影响渲染性能。

✅ 解决方案:

将样式抽离到外部定义,避免在 render 中动态创建:

const listItemStyle = (active) => css`
  color: ${active ? 'red' : 'black'};
  font-size: 16px;
`

function ListItem({ item, active }) {
  return <div css={listItemStyle(active)}>{item.name}</div>
}

这样即使多次调用 listItemStyle,emotion 也会缓存相同的样式规则,避免重复插入。


🕳️ 坑2:服务器端渲染(SSR)样式闪烁

我们项目中有部分页面需要 SSR,初次尝试时发现加载时出现明显的“闪白”现象:样式还没加载进来,内容先以默认样式渲染了。

✅ 解决方案:

确保客户端和服务端渲染出的 class 名一致,并正确注入关键 CSS。

使用 @emotion/server 提供的 extractCriticalToChunksrenderStylesToString

// Node.js 端处理
import { extractCriticalToChunks, renderStylesToString } from '@emotion/server'

const html = ReactDOMServer.renderToString(app)
const chunks = extractCriticalToChunks(html)
const emotionHTML = renderStylesToString(chunks)

// 最终拼接进 HTML 模板
res.send(`
  <html>
    <head>
      <style data-emotion="css">${emotionHTML}</style>
    </head>
    ...
  </html>
`)

🕳️ 坑3:调试和浏览器兼容性问题

开发过程中我们也遇到了一些调试相关的困扰:

  • Chrome DevTools 显示的是 emotion 自动生成的 class 名(如 _abcde_1),不易定位原始样式;
  • IE11 上偶尔出现样式不生效的问题;
  • 生产环境 build 后某些样式丢失。

✅ 解决方案:

  1. Chrome 调试增强: 安装插件 Emotion Devtools,可以展示原始 CSS 内容。

  2. IE11 兼容性处理: 在 emotion 配置中启用 auto-prefixer,使用 postcss-preset-env 来转换新语法。

  3. 防止样式丢失: 确保 Webpack 或 Rollup 正确配置了 emotion 插件,否则生产环境可能会因 tree-shaking 导致样式未正确引入。


五、实施效果:样式管理真的变轻松了吗?

CSS动画效果展示-1

迁移完成之后,我们团队对这次方案变化做了回顾总结,以下是几个显著的正向改变:

评估维度 传统 CSS CSS-in-JS(emotion)
样式冲突频率 几乎为零
样式可读性 分散 与组件紧密结合
动态逻辑支持 需额外 JS 处理 原生支持
调试效率 一般 初期略慢,后期更快
构建性能 中等(需合理拆分样式)
维护成本 随项目膨胀上升 保持稳定

特别是在组件封装方面,我们可以把完整的样式+逻辑打包成一个独立组件,对外只暴露有限 props,极大提升了复用性和可维护性。

举个真实场景:以前写一个表格列过滤控件,我们需要写一堆 class 名来控制隐藏/展开状态;现在可以直接通过 props 控制样式逻辑,代码清晰很多。


六、我的建议:根据项目情况做选择

虽然在这次重构中 CSS-in-JS 带来了不少好处,但我并不推荐所有人一股脑全换成 CSS-in-JS。是否采用这种方案,还是要结合项目的具体情况。

✅ 推荐使用 CSS-in-JS 的几种情况:

  • 项目是中大型 SPA,组件化程度高;
  • 需要动态主题切换、样式按状态变化的场景多;
  • 团队有一定 React 经验;
  • 使用 TypeScript,希望有更强的类型保障;
  • 强调组件封装和样式隔离。

❌ 不建议使用的几种情况:

  • 是纯静态页或内容站,不需要太多交互;
  • 项目技术栈锁定 jQuery,不打算重构;
  • 团队对 ES6/React 不熟悉;
  • 性能极其敏感(例如移动端优先、加载速度为核心指标);
  • 有历史包袱,短期内无法全面替换。

如果你不确定该选哪种方案,我的建议是:从小范围开始试点,逐步评估。

比如可以在新开发的模块中用 CSS-in-JS,旧模块维持原状,逐步过渡。这样可以最小化风险,也方便比较两者的优劣。


七、结尾:没有银弹,只有权衡的艺术

写到这里,突然想起之前一位前辈说过的一句话:

“技术选择就像买鞋子,再贵再好的品牌,穿不上自己的脚也没用。”

无论是传统 CSS 还是 CSS-in-JS,都不是万能钥匙。真正重要的不是用什么技术,而是清楚知道每种方案的边界和适用场景。

在这次重构中,CSS-in-JS 让我们更好地解决了项目初期遗留的样式管理难题,提高了协作效率和代码质量。但我也很清楚,这条路并非一帆风顺,前期投入的学习成本和技术适配也不能忽视。

希望你能从我的经历中获得一些启发,找到适合你们团队的那双“鞋”。

如果你也在用 CSS-in-JS,或者正在考虑是否采用,欢迎留言交流。我们一起探讨更多实践心得,少走弯路!


如果你觉得这篇文章对你有帮助,不妨点个赞、转发一下,让更多开发者看到~你的反馈是我持续输出的动力!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝