
在2025年的今天,我想说一句可能会被很多同行喷的话🤯:
我个人认为,以styled-components
和Emotion
为代表的、在运行时注入样式的CSS-in-JS技术,从长远来看,是一项失败的技术。
我知道,这个观点很暴论。
在它最火的那几年(大概2018-2022),我也是它的忠实拥护者。我们团队的好几个核心项目,都深度使用了styled-components
。它解决的局部作用域、基于状态的动态样式等问题,在当时确实是前端开发的巨大痛点。
但技术是在演进的。当初觉得是新技术,在今天看来,可能已经变成的很Low。
这篇文章,就是我想复盘一下,我们当初为什么爱上了它,又为什么在我们的新项目中,最终决定彻底放弃它。
我们当初为什么选择了它?
要客观地评价一项技术,首先要承认它的价值。CSS-in-JS在那个时代,确实解决了几个CSS开发的核心难题。
1. 完美的局部作用域 在CSS-in-JS出现之前,为了避免CSS类名全局冲突,我们发明了BEM这样的命名规范,或者使用CSS Modules。但它们要么依赖开发者的自觉,要么需要额外的构建配置。而CSS-in-JS通过自动生成唯一的类名,从根本上解决了这个问题,让我们可以毫无心智负担地给组件写样式。
2. 与组件状态的结合 这是它最吸引人的地方。我们可以非常优雅地,把组件的props
直接传递给样式,实现动态CSS。
jsx
// 这种写法,在当时看来,简直是天才般的优雅
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? 'palevioletred' : 'white'};
color: ${props => props.primary ? 'white' : 'palevioletred'};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// 使用
<Button>Normal</Button>
<Button primary>Primary</Button>
对比当时需要用className
拼接或者操作内联style
的繁琐,这种体验是降维打击。
那些我们无法再忍受的代价
当我们的项目越来越复杂,用户对性能的要求越来越高,CSS-in-JS的代价开始逐渐显现,并最终让我们无法忍受。
运行时性能损耗(The Performance Tax) 这是最致命的一点。
你的浏览器,在渲染页面时,不仅要解析和执行你的业务逻辑JS,还要额外花费时间和CPU,去解析你的样式JS,把它序列化成CSS字符串,然后再创建一个<style>
标签,动态地插入到DOM的<head>
里。
这个过程,在组件第一次渲染时(mount)都会发生。当页面上有大量动态组件时,这个运行时的开销,会显著地拖慢你的页面首次渲染速度(FCP)和可交互时间(TTI)。
除此之外,CSS-in-JS的运行时库本身,也增加了我们最终的打包体积。为了解决一个CSS的问题,却让我们的JS背上了更重的负担,这在性能优化的视角下,有点本末倒置。
与CSS生态的割裂 CSS-in-JS,名义上是CSS,但实际上,它让你脱离了整个CSS的生态系统。
- 工具链的割裂:很多强大的CSS静态分析工具、PostCSS插件,都无法处理你写在JS模板字符串里的伪CSS。
- 浏览器优化的割裂 :浏览器对原生CSS文件,有一套非常成熟的解析、渲染和缓存优化机制。而对于动态插入的
<style>
标签,浏览器能做的优化就非常有限了。比如,它无法在HTML解析阶段就并行下载和解析CSSOM。
不必要的复杂性 我们为了解决局部作用域和动态样式这两个问题,引入了一个庞大的运行时、一套新的语法(模板字符串里的CSS)、以及一套复杂的SSR(服务端渲染)方案。
这是一个典型的用牛刀杀鸡的方案。它引入的工程复杂性,远远超过了它解决的问题本身。
2025年,我们有了更好的选择
技术总是在发展的。当初我们选择CSS-in-JS的那些痛点,在2025年的今天,大部分已经被浏览器和现代工具链,用更优雅、代价更小的方式解决了。
-
针对局部作用域 : CSS Modules 我个人认为 依然是一个非常好的选择。它在构建时就完成了类名的哈希化,没有任何运行时开销。
-
针对动态样式 : CSS自定义属性(CSS Variables) 已经成为了所有现代浏览器的标配,它的能力远比我们想象的强大。我们可以轻松地把上面那个按钮的例子,用CSS Variables来改写:
css/* button.module.css */ .button { background: var(--button-bg, white); color: var(--button-text, palevioletred); /* ...其他样式 */ }
jsx// Button.jsx import styles from './button.module.css'; function Button({ primary, children }) { // 通过内联style来设置CSS变量的值 const style = primary ? { '--button-bg': 'palevioletred', '--button-text': 'white' } : {}; return <button className={styles.button} style={style}>{children}</button>; }
这种方式,既保留了动态能力,又没有任何运行时JS的性能损耗。
-
其它新的轮子: 社区也意识到了运行时CSS-in-JS的问题,催生了 零运行时的CSS-in-JS 方案,比如Linaria, Panda CSS。它们允许你用类似
styled-components
的语法来写样式,但在构建时,会把所有的样式都提取成静态的.css
文件,完美地解决了性能问题。
所以,我为什么说(运行时)CSS-in-JS是一项失败的技术?
它没有失败在它的解决思路上------它的思想在当时是革命性的。它失败在我们付出的代价上上。在2025年,我们有了太多开发代价更低、同样能解决问题的方案。
回顾它的兴衰,我作为一个亲历者,觉得它更像是一次有价值的坑。它用一种激进的方式,暴露了原生CSS的种种痛点,并倒逼CSS标准和前端社区,去寻找更好的、性能更高的解决方案。
作为WEB前端工程师,我们的工作就是不断地做权衡。放弃一个曾经深爱的技术,不是一种背叛,而是一种成长。
这是我从CSS-in-JS 实战中学到的宝贵经验🙂。