CSS-in-JS 还是传统 CSS?一个前端老司机的真实踩坑分享

谢思远_码农
2025-06-18 05:58
阅读 1013

开场白:从一次样式冲突说起

开场白:从一次样式冲突说起

那是我加入一家新公司后的第一天,项目组正准备重构一个中型的 React 管理后台。代码结构已经搭好了,模块划分也比较清晰。但当我第一次打开组件库时,愣了一下——有些组件用了传统的 .scss 文件,而有些则直接使用了 styled-components 写法。

那天晚上加班的时候,我在一个列表页里加了一个按钮样式。结果第二天测试提了个 bug:点击某个弹窗后,原本正常的按钮背景色变了!

这个问题让我开始重新思考:到底该用 CSS-in-JS 还是传统的 CSS?

作为一名写了 5 年前端的老兵,我觉得有必要把这个过程中的经验、教训都记录下来,和大家聊聊这个在现代前端开发中绕不开的话题。


背景与挑战:组件样式的“失控”时刻

背景与挑战:组件样式的“失控”时刻

项目背景

我们当时的项目是一个中后台系统,主要基于 React + TypeScript 搭建,UI 库用的是 Ant Design,部分自定义组件采用 Material UI 的风格进行二次封装。团队成员约有 8 人,前后端协作频繁,需求迭代节奏比较快。

遇到的问题

  1. 样式命名冲突
    大家写 .scss 的时候习惯性地用一些通用名,比如 .container.item 或者 .card。随着项目越来越大,这些名字很容易重复,导致意想不到的样式覆盖。

  2. 组件复用困难
    一些业务组件虽然长得差不多,但在不同页面下样式略有差异。为了“节省时间”,有的同事就干脆复制一份 SCSS 文件再改一改,久而久之造成了大量重复代码。

  3. 调试复杂
    在浏览器审查元素时发现,有些类名对应多个样式规则,甚至有些样式来自全局 CSS,有些是局部引入,很难快速定位问题根源。

  4. 跨团队协作困扰
    我们有个子产品线是由外包团队负责,他们习惯用 emotion,但我们主产品线统一用 styled-components。两个库一起上,虽然都能实现 CSS-in-JS,但维护起来特别乱。

这些问题最终集中爆发在一个“登录页样式错乱”的 PR 上,那次上线差点延迟……


解决思路:我们是怎么选的?

解决思路:我们是怎么选的?

作为一个经验尚可的开发者,我知道这场技术选型不能只靠“我喜欢哪个”。我们决定先从几个维度来评估:

  • 项目规模和团队人数
  • 是否需要高度组件化
  • 对构建性能的要求
  • 是否关注主题定制或动态样式
  • 团队当前的技术栈熟练程度

Step 1:回顾历史方案(传统 CSS)

✅ 优点

  • 学习成本低,大部分人都熟悉
  • 可以利用 SCSS、Less 等预处理器提升编写效率
  • 对 SEO 和 SSR 更友好(静态生成更可控)
  • 构建产物更小(无运行时 JS)

❌ 缺点

  • 类名容易冲突(尤其是多人协作时)
  • 样式复用不够灵活,容易出现“复制粘贴流”
  • 动态控制样式麻烦,需要额外绑定逻辑
  • 组件化能力弱,难以做到真正的“样式即组件”

使用场景建议:

适合小型项目、静态页面多的官网、SEO 强依赖的站点、或者团队对新技术接受度不高的情况。

Step 2:调研 CSS-in-JS 方案

当时主流的 CSS-in-JS 工具有几个选择:

工具 特点
styled-components 社区活跃,语法优雅,支持服务器端渲染
emotion 构建速度快,API 灵活,支持多种写法(包括对象语法)
twin.macro 结合 Tailwind 的便利性和 CSS-in-JS 的灵活性
vanilla-extract 完全运行时无关,CSS 提前构建,适合高性能要求

✅ 优点

  • 样式作用域天然隔离,避免冲突
  • 支持 props 动态传值,样式可以完全响应状态
  • 组件化强,样式和组件耦合度高,便于封装复用
  • 支持主题系统,方便做暗黑模式、主题切换等

❌ 缺点

  • 初学门槛稍高,需要适应模板字符串或对象写法
  • 构建速度慢于普通 CSS(特别是 styled-components)
  • 增加运行时体积(除非用像 vanilla-extract 这样的提前构建方案)
  • 某些库存在 SSR 不友好、服务端客户端样式不一致等问题

使用场景建议:

适合组件化要求高、动态样式频繁变化的中大型 SPA、需要主题定制的项目。


最终决定 & 实践方案落地

我们综合评估了以下几点:

移动端适配方案-2

  1. 团队中有两人熟悉 styled-components
  2. 项目未来会接入微前端架构,需要更强的样式隔离机制
  3. 主产品线希望未来能接入暗黑模式,以及根据用户偏好切换主题
  4. 构建性能不是最核心瓶颈(内部工具平台,构建时间在可接受范围内)

于是我们最终选择了 styled-components 作为主方案,理由如下:

  • API 直观,学习曲线适中
  • 支持完整的主题系统
  • 社区插件丰富(如 babel 插件优化类名可读性)
  • 与 React 无缝集成

同时我们也制定了几条规范:

  1. 所有公共组件必须封装成带有样式的完整组件(styled() 形式)
  2. 页面级样式仍然允许使用 SCSS 文件,但禁止全局定义
  3. 所有颜色、字体、间距等变量都从主题中取值,不再硬编码
  4. 使用 babel-plugin-styled-components 来优化调试体验(带类名提示)

具体应用中的实战经验分享

场景一:条件样式(Props 传参)

之前用 SCSS 时,经常要手动拼 class:

const className = cx('btn', {
  'btn-primary': primary,
  'btn-large': size === 'large',
});

现在直接写在 styled 组件中:

const Button = styled.button`
  background-color: ${props => (props.primary ? '#007bff' : '#6c757d')};
  padding: ${props => (props.large ? '1rem 2rem' : '0.5rem 1rem')};
`;

这不仅代码简洁了,而且在 IDE 中也更容易看懂每个 props 是如何影响样式的。

场景二:主题切换(暗黑模式)

以前我们要搞个暗黑模式,得手写两套 CSS 文件,并通过 class 控制切换。

用 styled-components 后只需要:

const ThemeToggle = () => {
  const [darkMode, setDarkMode] = useState(false);

  return (
    <ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
      <App />
    </ThemeProvider>
  );
};

然后在任何组件中都能访问 theme:

const Wrapper = styled.div`
  background-color: ${props => props.theme.background};
  color: ${props => props.theme.text};
`;

场景三:调试类名混乱问题

默认情况下,styled-components 输出的类名是随机字符(比如:sc-bdfBwQ jzJRrV),看起来很晕。

我们装了 babel-plugin-styled-components 后,类名变得直观多了:

// 原始类名
.sc-bdfBwQ.jzJRrV

// 加了 Babel 插件后的类名
.Button__StyledButton-sc-jpZxJk.cLZjHm

这样在 Chrome 审查元素时就能一眼看出是哪个组件的样式出错了。


效果总结:重构三个月后

响应式布局概念图-1

收益点

  • 样式冲突问题大幅减少
  • 主题系统实现比预期顺利
  • 新人学习成本低于预期(因为组件本身已封装好)
  • 调试效率显著提高(类名可见性强)

仍需注意的地方

  • 首次加载性能稍微下降(JS 运行时插入 <style> 的开销)
  • 部分浏览器兼容性处理要注意(如 IE11 的 CSSStyleSheet 支持问题)
  • 热更新偶尔会有样式残留问题(需配合 fast-refresh 正确配置)

心得体会 & 给你的建议

说实话,刚开始我也觉得“把 CSS 写进 JS 是不是有点反人类?”但后来慢慢意识到:

关键不是语法形式,而是解决实际问题的方式。

下面是我给各位朋友的一些实用建议:

🛠️ 技术选型别跟风,适合自己最重要

如果你们团队还在用 jQuery,或者是个简单的门户站,那就老老实实用 CSS/SCSS;但如果你们正在做一个组件库、多团队并行的产品、或者要做暗黑模式和主题定制,那真值得试试 CSS-in-JS。

💡 别怕“违反单一职责原则”这种老黄历

CSS-in-JS 并非“不好”,它只是换个角度去解决问题。组件本就应该包含视图、行为和样式,这才是真正意义上的“封装”。

🔍 推荐几个调试技巧

  • Chrome DevTools 里看 style 标签:很多 CSS-in-JS 的样式是动态插入的,记得看看文档 head 里的 style 标签。
  • 开启 styled-components 的 debug 模式:配合插件让你的类名有意义。
  • 用 React Developer Tools 查找组件:配合 styled 的类名提示,可以更快定位问题组件。

⚙️ 性能优化小技巧

  • 如果你追求极致性能,可以考虑 vanilla-extracttwin.macro 这种编译期生成 CSS 的方案。
  • 对 SSR 友好的项目,一定要正确注入服务器端的样式(例如 Next.js 下使用 getServerSideProps 注入主题)。

最后想说的:技术没有银弹

无论你是坚持传统 CSS 还是拥抱 CSS-in-JS,记住一句话:

没有最好的技术,只有最合适的方案。

我写这篇文章,不是想劝你也换掉你手上的 SCSS,而是想告诉你:在我这几年的实际工作中,CSS-in-JS 确实帮我们解决了不少痛点。但如果你的项目没那么多状态驱动的样式变化、也没那么复杂的组件复用需求,也许还是传统方式更适合你。

所以,下次再遇到“CSS 写哪”的争论时,不妨问问自己:

“我们现在面临的问题是什么?现有的方案能不能更好地解决它?”

别急着换轮子,先把路走稳。


Bonus 小彩蛋:我最喜欢的一个 styled-components 插件

最后推荐一个小工具:VS Code Styled Components 插件

它可以高亮模板字符串里的 CSS,并提供自动补全功能,让你写 styled 组件爽得飞起!


如果你也在经历类似的样式管理难题,欢迎留言交流。咱们互相切磋一下各自的实践心得。

前端路漫漫,愿你我共勉。🚀

评论 0

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