从“样式混乱”到“模块化管理”:我在项目中对 CSS-in-JS 与传统 CSS 的思考与选择

MySQL修理工
2025-06-29 18:18
阅读 343

开篇

开篇

我第一次真正意义上面对“CSS 应该怎么写”的问题,是在参与一个中大型 React 项目的重构过程中。那是一个用户量几十万、页面结构复杂且长期由多个团队维护的系统。由于历史原因,样式文件混乱不堪,全局污染严重,组件之间的样式互相影响,维护成本极高。

在这种背景下,我开始尝试各种方案来解决这个问题,其中包括传统的 CSS 模块化(CSS Modules)、BEM 等命名规范,以及后来风头正劲的 CSS-in-JS 方案,比如 styled-components 和 emotion。

这篇文章,我想结合自己的亲身经历,聊聊我在不同场景下如何权衡 CSS-in-JS 和传统 CSS,以及在真实项目中做出的选择和收获的经验。


我们遇到了什么问题?

我们遇到了什么问题?

JavaScript框架对比-2

1. 全局样式污染太严重

在一个老项目中,我们发现某个按钮的颜色总会在某些页面上变成红色,但明明代码里没设置。最后查了半天才发现,是某个第三方 UI 组件偷偷加了个 .btn 的 class,而我们的全局样式也用的是相同的类名。结果就是……按钮变成了红色。

这种情况太多了。每个新功能上线后都伴随着一堆意想不到的样式变化。

2. 样式难以复用,缺乏封装性

每次新增一个组件,都需要新建一个 CSS 文件,然后手动引入。更糟的是,组件之间共享的样式很难统一管理,常常出现重复定义的 margin、padding、颜色变量等。

3. 多人协作时命名冲突频繁

团队人数一多,大家都喜欢自己定义一套 class 命名方式。A 同学写 .card,B 同学也写 .card,C 同学还写了 .Card。最终这些样式全部作用在同一个 DOM 节点上,谁先执行谁赢。

这些问题让我意识到,传统的 CSS 写法虽然简单易懂,但在中大型项目里越来越显得捉襟见肘。


解决思路:引入 CSS-in-JS

为了应对这些问题,我们决定在项目中尝试使用 CSS-in-JS 技术,首选是 styled-components

一开始大家都有些抵触,觉得把样式写在 JS 里会很怪异。但我提出了几个关键优势:

  • 真正的局部作用域:不用担心命名冲突
  • 样式可继承、组合性强:可以像写组件一样“封装”样式逻辑
  • 动态样式的支持更方便:比如根据 props 动态计算 color、padding 等
  • 自动添加厂商前缀:无需再关心浏览器兼容性问题

实践示例

举个实际的例子:

// Button.js
import styled from 'styled-components';

const Button = styled.button`
  padding: 10px 20px;
  background-color: ${props => props.primary ? '#4CAF50' : '#ccc'};
  border-radius: 8px;
  color: white;
  font-size: 16px;
`;

export default Button;

在这个例子中:

  • 我们定义了一个带基础样式的按钮
  • 可以通过传入 primary prop 控制按钮样式
  • 所有样式都是局部作用域,不会污染其他组件

这比原来的写法清晰太多了。


使用后的收益和挑战

用户交互流程图-1

收益明显:

  1. 开发效率提升:不用来回切换 HTML/JS/CSS 文件,直接在组件内定义样式。
  2. 减少了命名焦虑:你再也不用担心 btn, button, Button, my-button 到底该用哪个了。
  3. 更好的样式封装能力:你可以把一组样式逻辑封装成一个基础组件,后续只需调用即可。
  4. 提高了可维护性:组件样式变更只需要修改对应 JS 文件,不需要找一大堆关联的 CSS 文件。

但也确实存在一些挑战:

  1. 学习曲线略高:尤其是刚接触的新人需要一段时间适应。
  2. 调试体验初期不友好:样式类名是随机生成的,刚开始调试时有点懵逼,不过现在大多数 DevTools 插件已经能识别 styled-components 了。
  3. 构建性能小幅下降:CSS-in-JS 方案在运行时生成样式,首次加载速度略有影响。
  4. SEO 或 SSR 友好度略差:如果服务端没有正确处理,可能会导致 FOUC(内容闪现)。

为什么不是所有项目都适合 CSS-in-JS?

在我们尝试全面转向 CSS-in-JS 的过程中,我也逐渐意识到,并不是所有项目都适合这种风格。

场景一:小型静态展示页

如果你只是做一个简单的介绍页或营销页,用不到很多交互逻辑,也没有太多组件复用的需求,那传统的 CSS 就足够了。而且它更轻量,构建更快。

场景二:多人协作 + 长期维护的中大型项目

这类项目往往存在以下特点:

  • 团队成员流动性大
  • 组件复用率高
  • 样式结构复杂
  • 需要动态控制样式的能力

这个时候 CSS-in-JS 就体现出它的优势来了:模块化强、封装性好、便于组织和管理。

场景三:追求极致性能优化的项目

CSS-in-JS 在运行时会生成 <style> 标签注入文档头部,虽然 emotion 有提取静态样式的能力,但如果非常注重首屏加载速度,传统的 CSS 加 BEM 依然是更稳妥的方式。


最终我们在项目中是怎么做的?

经过几个月的实践和打磨,我们逐步形成了一套混合使用的策略:

对于核心组件库、高频复用组件:

优先使用 CSS-in-JS(如 emotion),实现样式封装和按需加载。

对于布局级组件、通用样式(如字体大小、颜色变量):

依然保留部分传统 CSS(SCSS),配合 CSS Modules 使用。

引入 Tailwind?我们也在探索!

随着 Tailwind CSS 的流行,我们也开始在某些页面快速开发中尝试使用它。尤其是在 Landing Page、表单页等对布局要求高但交互少的页面,Tailwind 提供了极大的便利。

不过对于交互丰富的组件,我们还是更倾向使用 CSS-in-JS 的方式去管理样式,因为它更容易嵌套、组合和条件控制。


我的一些建议和经验分享

✅ 适合尝试 CSS-in-JS 的情况:

  • 团队规模 3+,存在多人协作
  • 项目周期较长,需要维护
  • 组件复用率高
  • 样式逻辑比较复杂,需要动态控制
  • 已经遇到样式冲突问题

❌ 不建议过度使用的场景:

  • 极小的静态页面
  • SEO 极其敏感的站点
  • 需要极致性能优化(首屏加载)
  • 团队对 CSS-in-JS 完全陌生且无培训计划

📌 一些实际开发技巧:

1. 使用 ThemeProvider 实现主题系统

import { ThemeProvider } from 'styled-components';

const theme = {
  primaryColor: '#4CAF50',
  fontSize: '16px'
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      {/* 你的组件 */}
    </ThemeProvider>
  );
}

这样可以在任何组件中通过 props.theme.xxx 获取主题变量,非常适合做暗黑模式切换等需求。

2. 样式继承简化写法

const PrimaryButton = styled(Button)`
  background-color: #2196F3;
`;

这种方式可以继承已有样式,仅覆盖部分属性,大大减少冗余代码。

3. 与 TypeScript 结合良好

CSS-in-JS + TypeScript 是绝配,可以精准控制 props 类型和样式逻辑,提升类型安全。

4. 注意样式文件过大问题

CSS-in-JS 的样式会被插入到 <head> 中,过多的组件可能导致 <style> 标签过多。这时可以通过 emotion 的插件进行样式抽取和合并。


写在最后

作为一个经历过传统 CSS 时代也拥抱现代工具链的开发者,我的体会是:没有绝对的好坏,只有适不适合。

CSS-in-JS 并不是银弹,也不是必须的。但它确实在组件化、模块化的大趋势下,为我们提供了一种全新的解决方案。尤其在 React 生态中,这种写法几乎成了标配。

如果你的项目也开始面临样式混乱、命名冲突、难以维护的问题,不妨试试 CSS-in-JS,或许它正是你需要的那一剂“良药”。

当然,在使用的过程中一定要关注性能、调试和维护性,不要为了炫技而增加不必要的复杂度。

希望我的这段经历能给你带来一些启发。欢迎留言讨论你们在项目中遇到的样式管理难题,我们一起探讨最佳实践 😊

评论 0

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