自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么我要含淚揮別 CSS-in-JS?

開發(fā) 前端
本文深入探討了運(yùn)行時(shí)CSS-in-JS的優(yōu)點(diǎn)和缺點(diǎn)。作為開發(fā)人員,我們需要評(píng)估這些優(yōu)缺點(diǎn),然后就該技術(shù)是否適合使用案例做出明智的決定。對(duì)于我來(lái)說(shuō),Emotion的運(yùn)行時(shí)性能成本遠(yuǎn)遠(yuǎn)超過(guò)了DX方面的好處,尤其是考慮到Sass模塊+實(shí)用程序類的替代方案仍然具有良好的DX,同時(shí)也提供了非常卓越的性能。

這篇文章將深入探討最初吸引我使用CSS-in-JS以及后來(lái)又決定放棄的原因。如果你對(duì)CSS-in-JS背后的邏輯感興趣,建議耐心看完本文。

CSS-in-JS是什么?

顧名思義,CSS-in-JS允許你通過(guò)在JavaScript或TypeScript代碼中直接編寫CSS來(lái)設(shè)置React組件的樣式:

// @emotion/react (css prop), with object styles
function ErrorMessage({ children }) {
return (
<div
css={{
color: 'red',
fontWeight: 'bold',
}}
>
{children}
</div>
);
}

// styled-components or @emotion/styled, with string styles
const ErrorMessage = styled.div`
color: red;
font-weight: bold;
`;

styled-components[1]和Emotion[2]是React社區(qū)中最受歡迎的CSS-in-JS庫(kù)。

本文重點(diǎn)介紹運(yùn)行時(shí)CSS-in-JS,包括styled-components和Emotion。運(yùn)行時(shí)CSS-in-JS僅表示庫(kù)在應(yīng)用程序運(yùn)行時(shí)解析并應(yīng)用樣式。并將在本文末尾簡(jiǎn)要討論編譯時(shí)CSS-in-JS。

CSS-in-JS的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

1.可以限定樣式應(yīng)用的范圍。 在編寫純CSS時(shí),很容易不小心擴(kuò)大樣式的應(yīng)用范圍。例如,假設(shè)你正在創(chuàng)建一個(gè)列表視圖,每行都需要有一些填充和邊框。你可能會(huì)這樣寫CSS:

.row {
padding: 0.5rem;
border: 1px solid #ddd;
}

幾個(gè)月后,你完全忘記了這個(gè)列表視圖,又創(chuàng)建了另一個(gè)具有列表行的組件。當(dāng)然,你可以在這些元素上設(shè)置className="row"?,F(xiàn)在,新組件的列表行有一個(gè)難看的邊框,你不知道為什么!雖然這種類型的問(wèn)題可以通過(guò)使用更長(zhǎng)的類名或更具體的選擇器來(lái)解決,但作為開發(fā)人員,你需要確保沒(méi)有類名沖突。

CSS-in-JS通過(guò)控制樣式的默認(rèn)應(yīng)用范圍來(lái)完全解決此問(wèn)題。如果將列表視圖行編寫為:

<div css={{ padding: '0.5rem', border: '1px solid #ddd' }}>...</div>

那么,填充和邊框就不可能意外地應(yīng)用于不相關(guān)的元素。

注意:CSS模塊也提供本地范圍的樣式。

2.集中放置。 如果使用普通CSS,那么你可以把所有.css文件放在src/styles目錄,同時(shí)所有React組件放在src/components中。但是隨著應(yīng)用程序規(guī)模的增長(zhǎng),很快就會(huì)難以判斷每個(gè)組件使用哪種樣式。于是這樣的CSS很多時(shí)候就會(huì)變成死代碼,因?yàn)闆](méi)有簡(jiǎn)單的方法可以判斷這些樣式有沒(méi)有被使用過(guò)。

組織代碼的更好方法是,將與單個(gè)組件相關(guān)的所有內(nèi)容都放在同一位置。這種做法稱為集中放置。

問(wèn)題在于,這在使用純CSS時(shí)很難實(shí)現(xiàn),因?yàn)镃SS和JavaScript必須放在單獨(dú)的文件中,并且無(wú)論.css文件位于何處,都將全局應(yīng)用樣式。另一方面,如果使用的是CSS-in-JS,則可以直接在使用它們的React組件中編寫樣式!如果操作正確,這將大大提高應(yīng)用程序的可維護(hù)性。

注意:CSS模塊還允許將樣式與組件一起放置,即使不在同一文件中。

3.可以在樣式中使用腳本變量。 CSS-in-JS使你能夠在樣式規(guī)則中引用JavaScript變量,例如:

// colors.ts
export const colors = {
primary: '#0d6efd',
border: '#ddd',
/* ... */
};

// MyComponent.tsx
function MyComponent({ fontSize }) {
return (
<p
css={{
color: colors.primary,
fontSize,
border: `1px solid ${colors.border}`,
}}
>
...
</p>
);
}

如本例所示,你可以在CSS-in-JS樣式中使用JavaScript常量(例如colors)和React prop/state(例如fontSize)。在某些情況下,在樣式中使用JavaScript常量的功能減少了重復(fù)代碼,因?yàn)椴槐貙⑼怀A考榷x為CSS變量又定義為JavaScript常量。使用props和state的功能允許你創(chuàng)建具有高度可自定義樣式的組件,而無(wú)需使用內(nèi)聯(lián)樣式。(當(dāng)相同的樣式應(yīng)用于許多元素時(shí),內(nèi)聯(lián)樣式對(duì)于性能并不友好。)

缺點(diǎn)

1.CSS-in-JS會(huì)增加運(yùn)行時(shí)開銷。當(dāng)呈現(xiàn)組件時(shí),CSS-in-JS庫(kù)必須將樣式“序列化”為可插入到文檔中的純CSS。很明顯,這會(huì)占用額外的CPU開銷。

2.CSS-in-JS會(huì)增加Bundle的大小。這是一個(gè)顯而易見的問(wèn)題——每個(gè)訪問(wèn)網(wǎng)站的用戶都必須下載CSS-in-JS庫(kù)。Emotion是7.9kB壓縮包,而styled-components是12.7kB。雖然這兩個(gè)庫(kù)都不是很大,但加在一起就不是了(react + react dom是44.5kB)。

3.CSS-in-JS會(huì)擾亂React開發(fā)工具。對(duì)于使用css prop的每個(gè)元素,Emotion將呈現(xiàn)<EmotionCssPropInternal>和<Insertion>組件。如果你在許多元素上使用css prop,那么Emotion的內(nèi)部組件確實(shí)會(huì)使React DevTools變得混亂,如下所示:

圖片

最可怕的幾個(gè)地方

1.頻繁插入CSS會(huì)迫使瀏覽器做很多額外的工作。在并發(fā)渲染中,React將會(huì)在渲染之間讓步于瀏覽器。如果你在組件中插入一個(gè)新CSS規(guī)則,那么瀏覽器必須先查看這些CSS規(guī)則是否適用于現(xiàn)有的DOM樹,因此會(huì)重新計(jì)算樣式規(guī)則。之后React渲染下一個(gè)組件,該組件發(fā)現(xiàn)一個(gè)新規(guī)則,然后同樣的情況再次發(fā)生。

這就導(dǎo)致了在React渲染時(shí)每幀針對(duì)所有DOM節(jié)點(diǎn)會(huì)重新計(jì)算所有CSS規(guī)則。

關(guān)于這個(gè)問(wèn)題最糟糕的是,這不是一個(gè)可修復(fù)的問(wèn)題(在運(yùn)行時(shí)CSS-in-JS的上下文中)。運(yùn)行時(shí)CSS-in-JS庫(kù)通過(guò)在組件呈現(xiàn)時(shí)插入新的樣式規(guī)則來(lái)工作,這對(duì)基本的性能是不利的。

2.使用CSS-in-JS可能會(huì)出現(xiàn)更多錯(cuò)誤,尤其是在使用SSR或組件庫(kù)時(shí)。在Emotion GitHub存儲(chǔ)庫(kù)中,存在的問(wèn)題有:

  • 一次加載多個(gè)Emotion實(shí)例。
  • 組件庫(kù)通常不能讓你完全控制樣式的插入順序。
  • Emotion的SSR支持在React 17和React 18之間的工作方式不同。這對(duì)于與React 18的流式服務(wù)端渲染兼容是必要的。

這些缺點(diǎn)還只是冰山一角。

深入探討性能

很明顯,運(yùn)行時(shí)CSS-in-JS既有重要的優(yōu)點(diǎn),也有顯著的缺點(diǎn)。為了說(shuō)明為什么我選擇遠(yuǎn)離這項(xiàng)技術(shù),我們需要探索CSS-in-JS對(duì)實(shí)際性能的影響。

渲染內(nèi)部 vs. 渲染外部的序列化

樣式序列化是指Emotion獲取CSS字符串或?qū)ο髽邮讲⑵滢D(zhuǎn)換為可插入到文檔中的純CSS字符串的過(guò)程。Emotion還會(huì)在序列化期間計(jì)算普通CSS的哈希值——此哈希值是你在生成的類名中所看到的內(nèi)容,例如.css-15nl2r3。

Emotion文檔在渲染中執(zhí)行序列化的示例,如下所示:

function MyComponent() {
return (
<div
css={{
backgroundColor: 'blue',
width: 100,
height: 100,
}}
/>
);
}

每次渲染MyComponent時(shí),都會(huì)再次序列化對(duì)象樣式。如果MyComponent頻繁渲染(例如,在每次擊鍵時(shí)),那么重復(fù)的序列化可能會(huì)產(chǎn)生很高的性能開銷。

提高性能的方法是將樣式移到組件外部,以便在加載模塊時(shí)進(jìn)行一次序列化,而不是在每次渲染時(shí)都進(jìn)行序列化。@emotion/react的css函數(shù)可以做到這一點(diǎn):

const myCss = css({
backgroundColor: 'blue',
width: 100,
height: 100,
});

function MyComponent() {
return <div css={myCss} />;
}

當(dāng)然,這會(huì)阻止你訪問(wèn)樣式中的prop,因此會(huì)錯(cuò)過(guò)CSS-in-JS的主要優(yōu)點(diǎn)之一。

在使用Emotion時(shí),對(duì)Member瀏覽器進(jìn)行基準(zhǔn)測(cè)試

下面是一個(gè)關(guān)于Member瀏覽器的簡(jiǎn)單列表視圖。幾乎所有Member瀏覽器的樣式都使用Emotion,特別是css prop。

圖片

在此次測(cè)試中:

Member瀏覽器將顯示20個(gè)用戶,

將刪除列表項(xiàng)周圍的React.memo,

強(qiáng)制最頂層<BrowseMembers>組件每秒渲染一次,并記錄前10次渲染的時(shí)間。

關(guān)閉React嚴(yán)格模式。

使用React DevTools分析該頁(yè)面,前10次渲染時(shí)間的平均值為54.3毫秒。

我個(gè)人的經(jīng)驗(yàn)法則是,React組件的渲染時(shí)間應(yīng)該為16毫秒或更短的時(shí)間,因?yàn)橐悦棵?0幀的速度渲染,渲染1幀是16.67毫秒。Member瀏覽器目前是這個(gè)數(shù)字的3倍以上,所以是一個(gè)非常重量級(jí)的組件。

該測(cè)試是在M1 Max CPU上執(zhí)行的,此CPU比普通用戶擁有的CPU快得多。在功能較弱的計(jì)算機(jī)上,54.3毫秒的渲染時(shí)間甚至很容易達(dá)到200毫秒。

分析火焰圖

以下是上述測(cè)試中單個(gè)列表項(xiàng)的火焰圖:

圖片

正如你所看到的,有大量的<Box>和<Flex>組件正在渲染——這些都使用css prop。雖然每個(gè)<Box>組件只需要0.1–0.2毫秒的渲染時(shí)間,但因?yàn)?lt;Box>組件的總量大,所以總的耗時(shí)將是巨大的。

在沒(méi)有用Emotion時(shí),對(duì)Member瀏覽器進(jìn)行基準(zhǔn)測(cè)試

為了了解這種昂貴的渲染代價(jià)有多少是由于Emotion造成的,我重寫了Member瀏覽器樣式,使用的是Sass模塊而不是Emotion。(Sass模塊在構(gòu)建時(shí)編譯為普通CSS,因此使用時(shí)幾乎沒(méi)有性能損失。)

重復(fù)與上述相同的測(cè)試,前10次渲染的平均值是27.7毫秒。比原來(lái)減少了48%!

所以,這就是我們與CSS-in-JS說(shuō)再見的原因:運(yùn)行時(shí)性能成本太高了。

免責(zé)聲明:如果你的代碼庫(kù)以更高性能的方式使用Emotion(例如,在渲染之外進(jìn)行樣式序列化),那么移除CSS-in-JS后,看到的性能提升可能并不顯著。

如果你對(duì)此測(cè)試感興趣,以下是原始數(shù)據(jù):

圖片

新的樣式系統(tǒng)

在下定決心要擺脫CSS-in-JS之后,有一個(gè)問(wèn)題馬上擺在了我們面前:那么用什么來(lái)代替呢?理想情況下,我們想要一個(gè)性能類似于普通CSS的樣式系統(tǒng),同時(shí)盡可能多地保留CSS-in-JS的好處。也就是說(shuō)最好具有:

可以控制樣式的應(yīng)用范圍。

樣式與應(yīng)用樣式的組件放在一起。

可以在樣式中使用腳本變量。

前面我說(shuō)過(guò)CSS模塊也提供樣式應(yīng)用范圍控制和集中放置的能力。CSS模塊編譯為普通的CSS文件,因此使用它們沒(méi)有運(yùn)行時(shí)性能成本。

但是,CSS模塊的主要缺點(diǎn)是,歸根結(jié)底,它們?nèi)匀皇瞧胀ǖ腃SS——而普通的CSS缺乏改善DX和減少代碼重復(fù)的功能。

幸運(yùn)的是,這個(gè)問(wèn)題有一個(gè)簡(jiǎn)單的解決方案——Sass模塊,用Sass編寫的CSS模塊。獲得CSS模塊的本地范圍樣式和Sass強(qiáng)大構(gòu)建功能的同時(shí),基本上沒(méi)有運(yùn)行時(shí)成本。這就是為什么Sass模塊將成為我們未來(lái)的通用樣式解決方案的原因。

實(shí)用程序類

我們團(tuán)隊(duì)對(duì)從Emotion切換到Sass模塊的一個(gè)擔(dān)憂是,應(yīng)用常見樣式,如display: flex會(huì)不太方便。以前:

<FlexH alignItems="center">...</FlexH>

如果僅使用Sass模塊執(zhí)行此操作,則必須打開.module.scss文件并創(chuàng)建一個(gè)應(yīng)用樣式display: flex和align-items: center的類。

為了改進(jìn)這方面的DX,我們決定引入一個(gè)實(shí)用程序類系統(tǒng)。實(shí)用工具類是在元素上設(shè)置單個(gè)CSS屬性的CSS類。通常組合多個(gè)實(shí)用程序類以獲得所需的樣式。上面的示例可以編寫為:

<div className="d-flex align-items-center">...</div>

Bootstrap和Tailwind是提供實(shí)用程序類的最流行的CSS框架。我已使用Bootstrap多年,所以選擇Bootstrap。雖然可以將Bootstrap實(shí)用程序類作為預(yù)構(gòu)建的CSS文件引入,但我們需要自定義類以適應(yīng)現(xiàn)有的樣式系統(tǒng),因此我將Bootstrap源代碼的相關(guān)部分復(fù)制到了項(xiàng)目中。

將Sass模塊和實(shí)用程序類用于新組件,我已經(jīng)使用幾個(gè)星期了,感到非常滿意。DX與Emotion相似,但運(yùn)行時(shí)性能要優(yōu)越得多。

附注:還可以使用typed-scss-modules[3]來(lái)為Sass模塊生成類型腳本定義。這樣做的最大好處是,允許我們定義一個(gè)像classnames[4]一樣工作的utils()幫助函數(shù)。但是不方便的一點(diǎn)是,它只接受有效的實(shí)用程序類名作為參數(shù)。

關(guān)于編譯時(shí)CSS-in-JS的說(shuō)明

本文重點(diǎn)介紹運(yùn)行時(shí)CSS-in-JS庫(kù),如Emotion和樣式化組件。最近,我發(fā)現(xiàn)有越來(lái)越多的CSS-in-JS庫(kù)在編譯時(shí)將樣式轉(zhuǎn)換為純CSS。包括:

  • Compiled[5]
  • Vanilla Extract[6]
  • Linaria[7]

這些庫(kù)旨在提供與運(yùn)行時(shí)CSS-in-JS類似的優(yōu)勢(shì),而不會(huì)降低性能成本。

雖然我自己沒(méi)有使用過(guò)編譯時(shí)CSS-in-JS庫(kù),但我仍然認(rèn)為與Sass模塊相比,它們有缺點(diǎn)。以下是我在查看編譯時(shí)看到的缺點(diǎn):

  • 當(dāng)組件首次掛載時(shí),仍會(huì)插入樣式,這會(huì)強(qiáng)制瀏覽器在每個(gè)DOM節(jié)點(diǎn)上重新計(jì)算樣式。
  • 動(dòng)態(tài)樣式(如示例中的color prop)無(wú)法在構(gòu)建時(shí)提取,因此編譯時(shí)使用style prop(也稱為內(nèi)聯(lián)樣式)將值作為CSS變量添加。已知內(nèi)聯(lián)樣式在應(yīng)用許多元素時(shí)會(huì)導(dǎo)致性能欠佳。
  • 庫(kù)仍會(huì)將樣式組件插入到React樹中。這將使React開發(fā)工具變得混亂,就像運(yùn)行時(shí)CSS-in-JS一樣。

總結(jié)

本文深入探討了運(yùn)行時(shí)CSS-in-JS的優(yōu)點(diǎn)和缺點(diǎn)。作為開發(fā)人員,我們需要評(píng)估這些優(yōu)缺點(diǎn),然后就該技術(shù)是否適合使用案例做出明智的決定。對(duì)于我來(lái)說(shuō),Emotion的運(yùn)行時(shí)性能成本遠(yuǎn)遠(yuǎn)超過(guò)了DX方面的好處,尤其是考慮到Sass模塊+實(shí)用程序類的替代方案仍然具有良好的DX,同時(shí)也提供了非常卓越的性能。

參考資料

[1]styled-components: https://styled-components.com/

[2]Emotion: https://emotion.sh/

[3]typed-scss-modules: https://www.npmjs.com/package/typed-scss-modules

[4]classnames: https://www.npmjs.com/package/classnames

[5]Compiled: https://compiledcssinjs.com/

[6]Vanilla Extract: https://vanilla-extract.style/

[7]Linaria: https://linaria.dev/

責(zé)任編輯:武曉燕 來(lái)源: 前端新世界
相關(guān)推薦

2022-11-11 08:16:51

2021-02-11 09:01:32

CSS開發(fā) SDK

2022-09-22 16:03:07

CSS-in-JS代碼

2012-06-18 14:51:09

Python

2023-11-01 08:36:07

CSSTailwind

2022-03-22 09:07:34

開發(fā)CSS技術(shù)

2022-12-13 09:59:25

計(jì)算遷移

2025-01-15 09:06:58

CSSRegEx前端

2019-05-14 11:15:51

微軟員工上市公司

2020-03-03 15:31:47

ReactVue前端

2019-04-22 14:20:08

區(qū)塊鏈數(shù)字貨幣比特幣

2009-09-29 16:24:11

2017-05-19 16:40:41

AndroidKotlin開發(fā)者

2016-05-18 10:15:25

PythonNode.js

2013-03-12 14:30:09

Ubuntu操作系統(tǒng)

2015-08-06 10:14:15

造輪子facebook

2022-08-15 08:27:02

基站網(wǎng)絡(luò)

2020-11-16 08:16:08

外企國(guó)企工作

2017-10-25 09:50:51

Linux

2012-02-28 09:11:51

語(yǔ)言Lua
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)