組件級別的 CSS-in-JS
在 2022 年 11 月 18 日,我們發(fā)布了 Ant Design 5.0 的正式版本,同時帶入大家視野中的還有 Ant Design 獨特的 CSS-in-JS 方案。通過這個方案,Ant Design 獲得了相較于其他 CSS-in-JS 庫更高的性能,但代價則是犧牲了其在應用中自由使用的靈活性。所以我們把它稱為“組件級”的 CSS-in-JS 方案。
CSS-in-JS 的困境
在 CSS-in-JS 中,hash 會用于確認一段 style 是否已經(jīng)插入。而計算 hash 的方法通常是將一段完整的 css 轉換為 hash 值。比如在 emotion 中,我們檢查頁面中的元素就可以看到這樣的 style 標簽,這樣的 style 標簽對應的 hash 值每一段都是不一樣的:

如此便可以引入一個 CSS-in-JS 被詬病已久的問題:我們在編寫代碼時寫的并不是最終的 css,所以每次都需要重新序列化得到 css 后再次計算 hash,這就在每次渲染組件時帶來了額外的開銷。如果你的頁面或者組件帶有非常復雜或者大量的 CSS-in-JS 代碼,甚至樣式會跟隨組件的 props 變化,那么這個性能消耗便變得不可忽視。?
針對這個問題,各個 CSS-in-JS 庫會有自己的應對方式,這里就先不做贅述,讓我們來看一看 Ant Design 的方案。
計算 hash
其實我們不難發(fā)現(xiàn),問題其實在于序列化 css 的過程。如果通過緩存的方法去減少序列化 css 的次數(shù)呢?對于應用級的 CSS-in-JS 來說,我們很難去找到一個很合適的 key 去確認緩存。但是如果是組件庫的話,最終得到的樣式則是比較穩(wěn)定的。根據(jù)我們從 v4 及之前版本確定下來的樣式結構,每一個組件的樣式在相同的主題變量和相同的版本下是不會改變的。反過來說,只有修改了主題變量,或者改變了 antd 的版本,樣式才可能會變化。由此我們得到了一個非常簡單的計算 hash 的方法:

我們會對所有的 antd 組件應用相同的?hash。如此一來,使用 antd 組件時,我們只會對當前的版本和主題變量進行 hash 計算,而前者可以直接由 package.json中得到,后者可以直接從 context 中得到,所以我們并不需要進行繁重的序列化 css 的操作,就可以得到穩(wěn)定的 hash,從而大幅地減少性能消耗。
組件緩存
通過上述的方式,我們邁出了『組件級』CSS-in-JS 的第一步,但是這還不夠。既然是『組件級』,那我們也可以針對組件再次進行優(yōu)化。?
在 Ant Design 中,一個組件的樣式通常來說是“完整”的,也就是說不管這個組件有什么樣的變體,他的樣式會一并存在于組件樣式中。如此我們可以再次得到一個結論:antd 組件的 props?不會影響組件樣式。這是非常重要的一點,在應用級的 CSS-in-JS 方案中,由于存在 props 影響組件樣式的可能,所以不可避免地會在渲染階段重新生成組件樣式,不管如何去優(yōu)化這一點仍無法忽視。既然我們采用了“組件級”的方案,那么這個問題就可以很輕松地解決:對組件做樣式緩存。在 hash 相同的情況下,同一個組件無論使用了多少次、渲染了多少次,樣式永遠只會在第一次 mount 時生成一次,剩下的時間里都會命中緩存,這便是『組件級』CSS-in-JS 方案的第二重保險。
Benchmark
在 Ant Design 5.0 的發(fā)布會上,我們簡單地做了一次 benchmark,在這里可以做一些補充說明:

這個 benchmark 的成立條件是產(chǎn)生一段非常長的不會變更的樣式,以此來測試這三個庫的基本用法的性能??梢钥闯鲈?Ant Design 的“組件級”使用場景下,無論是初次渲染還是二次渲染,antd 都擁有性能上的優(yōu)勢。由于 styled 在處理穩(wěn)定的樣式時有一定優(yōu)化,所以這個 benchmark 中二次渲染的性能較好,但在有 props 參與樣式計算時仍會和 emotion 一樣受到重新計算的影響。
『組件級』的局限
在上述的對比中,其實并不能說 antd 一定優(yōu)于 styled 和 emotion,而是在 antd 的組件級使用場景下,我們做了相應的優(yōu)化以取得了性能上的優(yōu)勢。反過來說,由于『組件級』的局限性,antd 的 CSS-in-JS 方案并不能適用于日常構建應用。?
由于特殊的 hash 計算方法和組件緩存,在套用 antd 的 CSS-in-JS 方案時,開發(fā)者必須自己提供穩(wěn)定的 hash 和獨特的組件名。對于應用來說,像 css module 這樣的自動 hash 的能力反而是更為需要的,同時對應用中海量的組件進行緩存也需要額外的管理成本,一旦出錯問題是很難排查的。因此我們更加推薦在組件庫中使用“組件級”的 CSS-in-JS 方案。