首页技术专题博客目录我的收藏关于与联系

CSS-in-JS 的那些坑:从 styled-components 到运行时开销

用了一年多的 styled-components,最后发现首屏渲染慢了 200ms。这篇文章记录一下 CSS-in-JS 在实际项目中的各种问题,以及我们是怎么一步步优化和最终迁移的。

为什么选了 styled-components?

项目初期,团队觉得 CSS-in-JS 很酷,能解决作用域问题,还能用 JavaScript 的逻辑。styled-components 又是最流行的,文档也写得不错,就选了它。

刚开始确实很爽:不用想类名,props 传进来就能动态改样式,TypeScript 支持也不错。但项目做到后面,问题就来了。

第一个坑:运行时开销

最明显的问题是性能。我们做了个性能分析,发现首屏渲染时,styled-components 占用了大概 15% 的 JavaScript 执行时间。

原因很简单:每次组件渲染,styled-components 都要在运行时生成 CSS,然后插入到 <style> 标签里。组件多了,这个开销就明显了。

我们试过用 babel-plugin-styled-components 做编译时优化,确实好了一些,但问题还在。特别是 SSR 的时候,服务端生成的样式和客户端不一致,还会闪一下。

第二个坑:调试困难

DevTools 里看到的类名都是 sc-xxx-xxx 这种随机生成的,根本看不出是哪个组件。虽然可以用 displayName,但每次都要手动加,很麻烦。

更烦的是,有时候样式不生效,你根本不知道是 CSS 优先级问题,还是 props 没传对,还是样式被覆盖了。排查起来很费时间。

第三个坑:主题切换的性能问题

我们有个暗色模式切换功能。切换主题的时候,所有使用 theme 的组件都要重新计算样式。组件多了,切换一次要等个几百毫秒,体验很差。

虽然可以用 CSS 变量来解决,但 styled-components 的 ThemeProvider 机制决定了它必须走 JavaScript 这一套。后来我们改成用 CSS 变量 + 原生 CSS,切换瞬间就完成了。

第四个坑:Bundle 体积

styled-components 本身就有 50KB+(gzip 后),再加上运行时逻辑,对小型项目来说有点重。我们项目不大,但为了一个样式方案引入这么重的库,感觉不太值。

我们是怎么优化的?

一开始想优化,试了几个方案:

方案一:编译时提取

试过用 linaria,它能在编译时把样式提取成 CSS 文件。确实快了很多,但语法限制比较多,迁移成本高。

方案二:CSS Modules

最后我们决定迁移到 CSS Modules。虽然失去了动态样式的灵活性,但换来的是:

  • 零运行时开销
  • 更好的调试体验(类名就是文件名)
  • 更小的 Bundle 体积
  • 浏览器原生缓存支持

动态样式用 CSS 变量解决,复杂逻辑用 classnames 库。虽然代码看起来没那么"优雅",但性能和可维护性都好了很多。

什么时候该用 CSS-in-JS?

不是说 CSS-in-JS 不好,只是要看场景:

  • 组件库:样式和组件强耦合,用 CSS-in-JS 很合适。
  • 小型项目:性能要求不高,开发效率优先。
  • 动态样式多:需要大量基于 props 的动态样式。

但如果是:

  • 性能敏感的应用
  • 需要 SSR 的项目
  • 团队更熟悉传统 CSS

那还是用 CSS Modules 或者 Tailwind 更稳妥。

现在的方案

我们现在用的是 CSS Modules + CSS 变量 + PostCSS。开发体验不错,性能也上来了。虽然写起来没那么"函数式",但实际开发中,稳定和可维护比酷炫更重要。

如果以后有编译时优化的 CSS-in-JS 方案(比如 Vanilla Extract),可能会再试试。但现阶段,CSS Modules 够用了。

总结

CSS-in-JS 是个好工具,但别盲目跟风。选技术方案的时候,多考虑实际场景和团队情况。性能问题、调试困难、Bundle 体积这些,都是要提前想好的。

我们踩过的坑,希望你能避开。如果已经在用了,记得做好性能监控,及时发现问题。

评论区