CSS-in-JS本意是解决问题,却带来了性能下降、可读性差、调试困难及过度工程。它将样式与JS绑定,增加了运行时开销。现代CSS已有更好原生方案,应回归关注点分离。
译自:CSS-in-JS: The Great Betrayal of Frontend Sanity
作者:Alexander T. Williams
我们曾被承诺优雅,但得到的却是运行时 CSS 解析、难以阅读的类名以及地狱般的注水(hydration)错误。CSS-in-JS 本应将我们从全局命名空间的噩梦和样式面条代码中解脱出来。
然而,它却将我们包裹在一个光鲜的新混乱层中——这个层性能更差、可读性更糟,而且不知何故,要完成二十年前普通样式表就能完美完成的事情,竟然需要消耗更多的 CPU 周期。这不是进化;我称之为伪装成进步的过度工程。
从解放到性能锁定
当 CSS-in-JS 首次出现时,它感觉是革命性的。不再有全局泄漏,不再担心特异性冲突,不再有神秘的类名碰撞。我们可以将样式与组件共存,使一切模块化并“自包含”。
但蜜月期很快就结束了。开发人员很快意识到,在运行时生成样式并非一个小小的权衡——它是一个定时炸弹般的性能问题。最初旨在简化样式的方法,却变成了前端开发中引入的最昂贵的抽象之一。
通过将 CSS 生成与 JavaScript 执行绑定,我们有效地将表现层与逻辑层焊接在一起——这正是我们最初发明 CSS 时试图避免的事情。
在客户端动态计算 CSS 的想法听起来很巧妙,直到你在凌晨 3 点调试一个注水不匹配问题,因为某个 SSR 阶段在服务器和浏览器之间对类名的哈希方式不同。这种问题感觉不像是 bug,更像是信任营销文案的惩罚。CSS-in-JS 库让一个简单的按钮样式变得像依赖注入一样复杂。更别提为了让边框变蓝,包大小会膨胀多少。
通过将 CSS 生成与 JavaScript 执行绑定,我们有效地将表现层与逻辑层焊接在一起——这正是我们最初发明 CSS 时试图避免的事情。分离的优雅被包裹在 Hook 和上下文提供者中的内联混乱所取代。
便利的隐性性能税
开发人员通常用“开销可以忽略不计”的论点来为 CSS-in-JS 辩护。事实并非如此。运行时样式会带来可测量的性能下降——从解析字符串损失的毫秒,到样式重新计算的内存开销。任何涉及数字商务的事物都面临风险。
每当一个组件挂载时,系统都必须创建、注入,有时甚至去重样式标签。将这乘以数百个组件,你的渲染周期就变成了一团官僚混乱。
曾经是网络最大优势的——轻量级渲染——现在正被过度热心的抽象所破坏。
这并非假设。性能审计持续显示 CSS-in-JS 框架增加了网络和运行时成本。你可能在你的 16 核开发机器上没有注意到,但使用低端设备的用户肯定会注意到。曾经是网络最大优势的——轻量级渲染——现在正被过度热心的抽象所破坏。
这里的悲剧在于,浏览器已经拥有一个经过实战检验、优化过的样式处理系统:CSS。我们用一个 JavaScript 模仿品取代了它,这个模仿品更慢、更难调试,有时还会在页面刷新时消失。CSS-in-JS 没有让样式更快:它只是让调试更慢。
然而,这个循环仍在继续。框架维护者不断修补,试图让动态样式“感觉原生”。但你无法超越浏览器自身的样式引擎。这就像在一个轮子里面重建一个轮子——只不过这个轮子会内存泄漏,而且每周都需要 npm 更新。
开发者体验(DX)的幻影
支持者经常声称 CSS-in-JS 改善了开发者体验(DX)。他们是对的——直到你真正需要维护它。乍一看,它感觉现代且符合人体工程学。样式存在于你的组件文件中。作用域类看起来很干净。变量可访问。
但一旦代码库扩展,幻象就会破灭。你开始寻找缺失的 prop 插值, juggling 上下文提供者并重写逻辑——所有这些都因为你的样式无法在不重写一半组件树的情况下处理简单的覆盖。
调试 CSS-in-JS 感觉就像玩带有类哈希值的扫雷游戏。DevTools 变成了一个神秘的、混淆的选择器荒地。检查一个元素,你会发现诸如 .css-4kq0lj{margin:0 auto} 这样的东西——一点也不可读。当你有十几个样式化组件像一个功能失调的家族树一样相互继承时,祝你好运能追溯到它们的来源。
这种对“DX”(开发者体验)的痴迷已经成为架构债务的烟幕弹。
更糟糕的是,CSS-in-JS 鼓励过度工程。当你可以导入一个 Hook 并动态切换主题上下文时,为什么还要写一个简单的媒体查询呢?曾经争论是否使用 Flexbox 的开发人员现在争论哪种运行时样式在注水(hydration)时的性能提高了 2%。心智负担是巨大的。回报呢?充其量也只是微不足道。
这种对“DX”(开发者体验)的痴迷已经成为架构债务的烟幕弹。当然,导入 styled-components 并用反引号写 CSS 感觉很好。但当你的应用程序性能下降,构建时间翻倍时,这种多巴胺的快感很快就会消退。便利性不是工艺。而 CSS-in-JS 是以牺牲清晰度为代价的便利。
回归理智:CSS-in-JS 后的未来
值得庆幸的是,潮流正在转变。即使是最坚定的 CSS-in-JS 支持者也开始承认它在大规模应用中是不可持续的。Remix、Astro 和 Next.js 13 等框架正在促使开发人员回归简洁——利用传统的 CSS、CSS Modules 或静态提取,而不是运行时生成。信息很明确:关注点分离仍然很重要。
CSS 变量和容器查询以及作用域样式的兴起,意味着现代 CSS 现在能够处理 CSS-in-JS 试图解决的大部分问题——而且是原生的、高效的、可预测的。没有运行时开销。没有哈希冲突。没有无形的样式标签堵塞你的 DOM。只有即时加载且行为一致的样式。
CSS 没有坏;是我们的纪律坏了。
我们不需要重新发明样式。我们只需要尊重最初使网络得以运行的边界。CSS 没有坏;是我们的纪律坏了。答案不是更多的抽象——而是更好的理解。未来不是将 CSS 嵌入 JavaScript,而是编写能够随着网络的演进自然扩展的可维护样式。
回归基础只是成熟和向实用性正常转变的表现。它认识到,为了解决我们自己制造的问题而增加抽象层并不是创新。那只是纯粹的否认。
现代 CSS 解决了最初的问题
CSS-in-JS 出于良好的意图而诞生——模块化、可预测性和组件化。但我们得到的却是伪装成进步的复杂性。网络不需要运行时样式引擎或神秘的哈希值来保持现代化。它需要克制。它需要开发人员愿意接受并非每个问题都需要一个库。
我们正在进入一个新篇章,在这里,简洁再次成为复杂,全局样式表与作用域规则和平共存。浏览器承担了繁重的工作,一如它应有的样子。是时候停止崇拜那些拖慢我们速度的抽象,开始信任我们几十年来一直在改进的平台了。准备好行动了吗?