日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

這篇文章將深入的挖掘我當(dāng)時(shí)為什么會在項(xiàng)目中使用 css-in-JS (本文使用 Emotion 方案 ),而現(xiàn)在為什么正在放棄這樣的方案。

什么是 CSS-in-JS

CSS-in-JS 允許你直接使用 JAVAScript 或者 TypeScript 修改你的 React 組件的樣式

import styled from '@emotion/styled'

const ErrorMessageRed = styled.div`
  color: red;
  font-weight: bold;
`;

function App() {
  return (
   <div>
    <ErrorMessageRed>
      hello ErrorMessageRed !!
    </ErrorMessageRed>
   </div>
  );
}

export default App;

styled-components 和 Emotion 是 React 社區(qū)最流行的 CSS-in-JS 方案。本文中我只是提及到 Emotion ,但是我相信大部分的使用場景也同樣適用于 styled-components。

本文專注于 運(yùn)行時(shí)類型的 CSS-in-JS ,styled-components 和 Emotion 都屬于這個(gè)類型。因?yàn)?CSS-in-JS 還有另一種類型,編譯時(shí)類型 CSS-in-JS 這塊會在文章末段稍微提及到。

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

在我們深入了解 CSS-in-JS 的模式和它對性能的影響之前,我們先從總體的了解一下為什么我們會使用這項(xiàng)技術(shù)以及為什么要逐步放棄

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

1.Locally-scoped styles: 當(dāng)我們在裸寫 CSS 的時(shí)候,很容易就污染到其他我們意想不到的組件。比如我們寫了一個(gè)列表,每一行的需要加一個(gè)內(nèi)邊距和邊框的樣式。我們可能會寫這樣的 CSS 代碼

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

幾個(gè)月之后可能你已經(jīng)忘記了這個(gè)列表的代碼了,然后你寫了 className="row" 在另外的組件上,那么這個(gè)新的組件有了內(nèi)邊距合邊框樣式,你甚至都不知道為什么會這樣。你可以使用更長的類名或者更加明確的選擇器來避免這樣的情況發(fā)生,但是你還是無法完全保證不會再出現(xiàn)這樣的樣式?jīng)_突。

CSS-in-JS 就可以通過 Locally-scoped styles 來完全解決這個(gè)問題。如果你的列表代碼這么寫的話:

<div className={css`
        padding: 0.5rem;
        border: 1px solid #ddd;
    `}>
	...row item...
 </div>

這樣的話,內(nèi)邊距和邊框的樣式永遠(yuǎn)不會影響到其他組件。

提示:CSS Modules 也提供了 Locally-scoped styles

2. Colocation: 你的 React 組件是寫在 src/components 目錄中的,當(dāng)你裸寫 CSS 的時(shí)候,你的 .css 文件可能是放置在 src/styles 目錄中。隨著項(xiàng)目越來越大,你很難明確哪些 CSS 樣式是用在哪些組件上,這樣最后你會冗余很多樣式代碼。

一個(gè)更好的組織代碼的方式可能是將相關(guān)的代碼文件放在同個(gè)地方。這種做法成為「共置」,可以通過這篇文章了解一下。

問題在于其實(shí)很難實(shí)現(xiàn)所謂的「共置」。如果在項(xiàng)目中裸寫 CSS 的話,你的樣式和可能會作用于全局不管你的 .css 文件被放置在哪里。另一方面,如果你使用 CSS-in-JS,你可以直接在 React 組件內(nèi)部書寫樣式,如果組織得好,那么你的項(xiàng)目的可維護(hù)性將大大提升。

提示:CSS Modules 也提供了「共置」的能力

3. 在樣式中使用 JavaScript 變量: CSS-in-JS 提供了讓你在樣式中訪問 JavaScript 變量的能力


function App(props) {
    const color = "red";
    const ErrorMessageRed = styled.div`
      color: ${props.color || color};
      font-weight: bold;
    `;
    
    return (
        <div>
            <ErrorMessageRed>
              hello ErrorMessageRed !!
            </ErrorMessageRed>
        </div>
    );
}

上面的例子展示了,我們可以在 CSS-in-JS 方案中使用 JavaScript 的 const 變量 或者是 React 組件的 props。這樣可以減少很多重復(fù)代碼,當(dāng)我們需要同時(shí)在 JavaScript 和 CSS 兩側(cè)定義相同的變量的時(shí)候。我們通過這樣的能力可以不需要使用 inline styles 這樣的方式來完成高度自定義的樣式。( inline styles 對性能不是特別友好,當(dāng)我們有很多相同的樣式寫在不同的組件的時(shí)候)

中立點(diǎn)

1. 這是熱門的新技術(shù): 許多的開發(fā)者包括我自己,會更熱衷于使用 JavaScript 社區(qū)中熱門的新技術(shù)。一個(gè)重要的原因是,很多新的框架或者庫,能夠提升帶來巨大的性能或者體驗(yàn)上的提升(想象一下,React 對比 jQuery 帶來的開發(fā)效率提升)。另一個(gè)原因就是,我們對新技術(shù)抱有比較開放的態(tài)度,我們不愿意錯(cuò)過每個(gè)大事件。當(dāng)然了,我們在選擇新的技術(shù)的時(shí)候也會考慮到它帶來的負(fù)面影響。這大概就是我之前選擇 CSS-in-JS 的原因。

缺點(diǎn)

  1. CSS-in-JS 的運(yùn)行時(shí)問題。當(dāng)你的組件進(jìn)行渲染的時(shí)候,CSS-in-JS 庫會在運(yùn)行時(shí)將你的樣式代碼 ”序列化” 為可以插入文檔的 CSS 。這無疑會消耗瀏覽器更多的 CPU 性能
  2. CSS-in-JS 讓你的包體積更大了。 這是一個(gè)明顯的問題。每個(gè)訪問你的站點(diǎn)的用戶都不得不加載關(guān)于 CSS-in-JS 的 JavaScript。Emotion 的包體積壓縮之后是 7.9k ,而 styled-components 則是 12.7 kB 。雖然這些包都不算是特別大,但是如果再加上 react & react-dom 的話,那也是不小的開銷。
  3. CSS-in-JS 讓 React DevTools 變得難看。 每一個(gè)使用 css prop 的 react 元素, Emotion 都會渲染成 <EmotionCssPropInternal> 和 <Insertion> 組件。如果你使用很多的 css prop,那么你會在 React DevTools 看到下面這樣的場景

 

  1. 頻繁的插入 CSS 樣式規(guī)則會迫使瀏覽器做更多的工作。 React 團(tuán)隊(duì)核心成員&React Hooks 設(shè)計(jì)者 Sebasian 寫了一篇關(guān)于 CSS-in-JS 庫如何與 React 18 一起工作的文章。他特別說到

在 concurrent 渲染模式下,React 可以在渲染之間讓出瀏覽器的控制權(quán)。如果你為一個(gè)組件插入一個(gè)新的 CSS 規(guī)則,然后 React 讓出控制權(quán),瀏覽器會檢查這個(gè)新的規(guī)則是否作用到了已有的樹上。所以瀏覽器重新計(jì)算了樣式規(guī)則。然后 React 渲染下一個(gè)組件,該組件發(fā)現(xiàn)一個(gè)新的規(guī)則,那么又會重新觸發(fā)樣式規(guī)則的計(jì)算。

實(shí)際上 React 進(jìn)行渲染的每一幀,所有 DOM 元素上的 CSS 規(guī)則都會重新計(jì)算。這會非常非常的慢

更壞的是,這個(gè)問題好像是無解的(針對運(yùn)行時(shí) CSS-in-JS)。運(yùn)行時(shí) CSS-in-JS 庫會在組件渲染的時(shí)候插入新的樣式規(guī)則,這對性能來說是一個(gè)很大的損耗。

  1. 使用 CSS-in-JS ,會有更大的概率導(dǎo)致項(xiàng)目報(bào)錯(cuò),特別是在 SSR 或者組件庫這樣的項(xiàng)目中。在 Emotion 的 GitHub 倉庫,我們可以看到很多向如下的 issue

我在我的 SSR 項(xiàng)目中使用了 Emotion,但是它報(bào)錯(cuò)了,因?yàn)?hellip;….

在這些海量的 issue 中,我們可以找到一些共同特征:

  • 多個(gè) Emotion 實(shí)例被同時(shí)加載。如果多個(gè)被同時(shí)加載的實(shí)例是相同的Emotion 版本,這將會引起很多問題(比如說)
  • 組件庫通常無法讓您完全控制插入樣式的順序(比如說)
  • Emotion 的 SSR 能力支持對于 React 17 和 18 兩個(gè)版本是不相同的。我們需要做一些兼容性的工作來兼容 React 18 的 stream SSR(比如說)

相信我,上述的這些問題僅僅是冰山一角。

性能檢測

在這一點(diǎn)上,很明顯,CSS-in-JS 有著顯著的優(yōu)點(diǎn)和缺點(diǎn)。為了明白我們?yōu)槭裁凑谝瞥@項(xiàng)技術(shù),我們需要更加真實(shí)的 CSS-in-JS 性能場景。這里我們會著重關(guān)注 Emotion 對于性能的影響。Emotion 有很多種使用方式,每種方式都有其各自的性能表現(xiàn)特點(diǎn)。

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

樣式序列化指的是 Emotion 將你的 CSS 字符串或者樣式對象轉(zhuǎn)化成可以插入文檔的純 CSS 字符串。Emotion 同時(shí)也會在序列化的過程中根據(jù)生成的存 CSS 字符串計(jì)算出相應(yīng)的哈希值——這個(gè)哈希值就是你可以看到的動態(tài)生成的類名,比如 css-an61r6

在測試前,我預(yù)感到這個(gè)樣式序列化是在 React 組件渲染周期里面完成還是外面完成,將對 Emotion 的性能表現(xiàn)起到比較大的影響。

在渲染周期內(nèi)完成的代碼如下

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

每次 MyComponent 渲染,樣式對象都會被序列化一次。如果 MyComponent 渲染的比較頻繁,重復(fù)的序列化將有很大的性能開銷

一個(gè)性能更好的方案是把樣式移到組件的外面,所以序列化過程只會在組件模塊被載入的時(shí)候發(fā)生,而不是每次都要執(zhí)行一遍。你可以使用 @emotion/react 的 css 方法

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

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

當(dāng)然,這樣使得你無法在樣式種獲得組件的 props,所以你會錯(cuò)失 CSS-in-JS 的一個(gè)主要的賣點(diǎn)。

測試「成員檢索」功能

我們接下來將使用在一個(gè)頁面上實(shí)現(xiàn)「成員檢索」的能力,就是使用一個(gè)列表展示團(tuán)隊(duì)成員的一個(gè)簡單的功能。列表上幾乎所有的樣式都是通過 Emotion 來實(shí)現(xiàn),特別是使用 css prop

(為了保障信息安全,我截圖了網(wǎng)絡(luò)上一張類似的圖片,功能幾乎一樣)

 

測試如下:

  • 「成員檢索」會在頁面上顯示 20 個(gè)用戶
  • 去除 react.memo 對列表的包裹
  • 每秒都強(qiáng)制渲染 組件,記錄前 10 次渲染的時(shí)間
  • 關(guān)閉 React Strict 模式 (不然會觸發(fā)重復(fù)渲染,時(shí)間可能是現(xiàn)在的 2 倍)

我使用 React DevTools 進(jìn)行記錄,得到前 10 次的平均渲染時(shí)間為 54.3 毫秒。

以往的經(jīng)驗(yàn)告訴我,一個(gè) React 組件最好的渲染時(shí)間大概是 16 毫秒(每秒 60 幀計(jì)算)。 < BrowseMembers > 組件的渲染時(shí)間是經(jīng)驗(yàn)值的 3 倍左右,所以它是一個(gè)比較「重」的組件。

如果我去除 Emotion,而使用 Sass Modules 來實(shí)現(xiàn)頁面的樣式,平均的渲染時(shí)間大概是在 27.7 毫秒。這比原來使用 Emotion 少了將近 48% !!!

這就是為什么我們開始放棄使用 CSS-in-JS 的原因:運(yùn)行時(shí)的性能消耗實(shí)在太嚴(yán)重了!??!

我們的新樣式方案

在我們下定決心要移除 CSS-in-JS 之后,剩下的問題就是:我們應(yīng)該什么方案來代替。我們既想要有裸寫 CSS 這樣的性能,又想要盡可能保留 CSS-in-JS 的優(yōu)點(diǎn)。這里再次簡單梳理一下 CSS-in-JS 的優(yōu)點(diǎn)(忘記的同學(xué)可以翻回上面再看看):

  1. locally-scoped styles
  2. colocated
  3. 在 CSS 中使用 JS 變量

如果你有認(rèn)真看這篇文章,那你應(yīng)該還記得我在上文中提到,CSS Modules 其實(shí)也是可以提供 locally-scoped styles 和 colocated 這樣類似的能力的。并且 CSS Modules 編譯成原生 CSS 文件之后,沒有運(yùn)行時(shí)的性能開銷。

在我看來,CSS Modules 的缺點(diǎn)在于,他們依然是原生的 CSS —— 原生 CSS 缺少提升開發(fā)體驗(yàn)以及減少冗余代碼的能力。但是,如果當(dāng)原生CSS 具備 nested selectors 的能力之后,情況將會改善很多。

幸好,市面上已經(jīng)有了一個(gè)很簡單的方案來解決這個(gè)問題—— Sass Modules ( 使用 Sass 來寫 CSS Modules ) 。你既可以享受 CSS Modules 的 locally-scoped styles 能力,又可以享受 Sass 強(qiáng)大的編譯時(shí)功能(去除運(yùn)行時(shí)性能開銷)。這就是我們會使用 Sass Modules 的一個(gè)重要原因。

注意:使用 Sass Modules ,你將無法享受到 CSS-in-JS 的第 3 個(gè)優(yōu)點(diǎn)(在 CSS 中使用 JS 變量)。但是你可以使用 :export 塊將 Sass 代碼的常量導(dǎo)出到 JS 代碼中。這個(gè)用起來不是特別方便,但是會使你的代碼更加清晰。

Utility Classes

比較擔(dān)心我們團(tuán)隊(duì)從 Emotion 切換到 Sass Modules 之后,會在寫一些極度常用的樣式的時(shí)候不是很方便,比如 display: flex 。之前我們是這樣寫的

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

如果改用 Sass Modules 之后,我們需要?jiǎng)?chuàng)建一個(gè) .module.scss 文件,然后寫一個(gè) display: flex 和 align-item: center 。這不是世界末日,但肯定是不夠方便的。

為了提升開發(fā)體驗(yàn),我們決定引入一個(gè) Utility Classes。如果你對 Utility Classes 還不是很熟悉,用一句話概括就是,“他們是一些只包含一個(gè) CSS 屬性的 CSS 類”。通常情況下,你會在你的元素上使用多個(gè)這樣的類,通過組合的方式來修改元素的樣式。對于上面的這個(gè)例子,你可能需要這樣寫:

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

Bootstrap 和 Tailwind 是目前最流行的提供 Utility Classes 的解決方案。這些庫在設(shè)計(jì)方案上做了非常多的努力,這使得我們可以放心的使用他們,而不是自己重新搭建一個(gè)。因?yàn)槲沂褂?Bootstrap 已經(jīng)很多年了,所以我們選擇了 Bootstrap。我們使用 Bootstrap 作為我們項(xiàng)目的預(yù)設(shè)樣式方案。

我們已經(jīng)在新組件上使用 Sass Modules 和 Utility Classes 好幾個(gè)星期了。我們覺得都不錯(cuò)。它的開發(fā)體驗(yàn)跟 Emotion 差不多,但是運(yùn)行時(shí)的性能更加的好。

我們也使用 typed-scss-modules 來為 Sass Modules 生成 TypeScript 的類型文件。也許這樣做最大的好處就是允許我們定一個(gè)幫助函數(shù) utils() ,這樣我們可以像使用 classnames 去操作樣式。

一些關(guān)于 構(gòu)建時(shí)CSS-in-JS 方案

本文主要關(guān)注的是 運(yùn)行時(shí) CSS-in- JS 方案,比如 Emotion 和 styled-components 。最近,我們也關(guān)注到了一些將樣式轉(zhuǎn)換是純 CSS 的構(gòu)建時(shí)CSS-in-JS 方案。包括

  • Compiled
  • Vanilla Extract
  • Linaria

這些庫的目標(biāo)是為了提供類似于運(yùn)行時(shí) CSS-in-JS 的能力,但是沒有性能損耗。

目前我還沒有在真實(shí)項(xiàng)目中使用構(gòu)建時(shí) CSS-in-JS 方案。但我想這些方案對比 Sass Modules 大概會有以下的缺點(diǎn):

  • 依然會在組件 mount 的時(shí)候完成樣式的第一次插入,這還是會使得瀏覽器重新計(jì)算每個(gè) DOM 節(jié)點(diǎn)的樣式
  • 動態(tài)樣式無法被抽取出來,所以會使用 CSS 變量加上行內(nèi)樣式的方法來替代。過多的行內(nèi)樣式依然會影響性能
  • 這些庫依然會插入一些特定的組件到項(xiàng)目的 React 樹中,依然會導(dǎo)致 React DevTools 的可讀性變得比較差

結(jié)論

感謝你閱讀到這里~任何事情都是,有它好的一面也有它不好的一面。最終,作為開發(fā)人員,你必須評估這些優(yōu)缺點(diǎn),然后就該技術(shù)是否適合你的項(xiàng)目,然后做出決定。而對于目前我所在的團(tuán)隊(duì)來說,Emotion 帶來的運(yùn)行時(shí)性能消耗的影響已經(jīng)大于它帶來的開發(fā)體驗(yàn)的好處。而我們目前所使用的 Sass Modules 加上 Utility Classes 方案,在一定程度上也彌補(bǔ)了開發(fā)體驗(yàn)的問題。

分享到:
標(biāo)簽:CSS
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定