在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。
// 這種寫法,在當時看來,簡直是天才般的優雅
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的生態系統。
<style>
標籤,瀏覽器能做的優化就非常有限了。比如,它無法在HTML解析階段就並行下載和解析CSSOM。不必要的複雜性
我們為了解決局部作用域和動態樣式這兩個問題,引入了一個龐大的運行時、一套新的語法(模板字串裡的CSS)、以及一套複雜的SSR(伺服器端渲染)方案。
這是一個典型的用牛刀殺雞的方案。它引入的工程複雜性,遠遠超過了它解決的問題本身。
技術總是在發展的。當初我們選擇CSS-in-JS的那些痛點,在2025年的今天,大部分已經被瀏覽器和現代工具鏈,用更優雅、代價更小的方式解決了。
針對局部作用域:
CSS Modules 我個人認為依然是個非常好的選擇。它在構建時就完成了類名的哈希化,沒有任何運行時開銷。
針對動態樣式:
CSS自訂屬性(CSS Variables) 已經成為了所有現代瀏覽器的標配,它的能力遠比我們想像的強大。我們可以輕鬆地把上面那個按鈕的例子,用CSS Variables來改寫:
/* button.module.css */
.button {
background: var(--button-bg, white);
color: var(--button-text, palevioletred);
/* ...其他樣式 */
}
// 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的性能損耗。
styled-components
的語法來寫樣式,但在構建時,會把所有的樣式都提取成靜態的.css
文件,完美地解決了性能問題。所以,我為什麼說(運行時)CSS-in-JS是一項失敗的技術?
它沒有在解決思路上失敗——它的思想在當時是革命性的。它的失敗在於我們付出的代價上。在2025年,我們有了太多開發代價更低、同樣能解決問題的方案。
回顧它的興衰,我作為一個親歷者,覺得它更像是一次有價值的坑。它用一種激進的方式,暴露了原生CSS的種種痛點,並倒逼CSS標準和前端社區,去尋找更好的、更高性能的解決方案。
作為WEB前端工程師,我們的工作就是不斷地做權衡。放棄一個曾經深愛的技術,不是一種背叛,而是一種成長。
這是我從CSS-in-JS 實戰中學到的寶貴經驗🙂。