打破“防自學(xué)機制”,種草這幾個庫了解CSS-in-JS

目錄

  • 前言
  • JSS (JavaScript Style Sheets)
      1. react-jss
      1. @stylexjs/stylex
  • Styled Component
      1. styled-component
      1. @emotion/styled
  • twin.macro
    • 通過Babel宏提供簡便語法糖
    • 結(jié)合 Tailwind CSS 的實用性與 CSS-in-JS 的靈活性
  • antd在CSS-in-JS中的實踐
    • CSS架構(gòu)模式(v4.x)
    • CSS變量方案
    • CSS-in-JS方案
  • antd-style
    • JSS + css模版字符串

前言

“CSS-in-JS是一種方案術(shù)語,方案術(shù)語的起源通常源于幾個業(yè)界廣泛認可的庫,它們共同組成并逐漸發(fā)展出相應(yīng)的方案或環(huán)境。”

簡單來說 CSS-in-JS 就是將應(yīng)用的CSS樣式寫在JavaScript文件里面,而不是獨立為.css/less/scss之類的文件。
這樣它們就能擁有了JS語言特性的能力:模塊聲明、變量定義、函數(shù)調(diào)用和條件判斷等:

  • 動態(tài)生成樣式、包括自動添加瀏覽起前綴
  • 不會出現(xiàn)類名重復(fù)問題
  • 可以輕松地刪除無用CSS等

也許我們對術(shù)語的概念會模糊不清,不要擔(dān)心,通過探索以下這些具有相似動機和思想的庫,就能充分認識CSS-in-JS這個世界。

JSS (JavaScript Style Sheets)

JSS 是一類具體的 CSS-in-JS 實現(xiàn)庫,命名靈感來自JavaScript Style Sheets(JSSS),
語法風(fēng)格偏向函數(shù)式,在CSS-in-JS可以作為一個大類以區(qū)分。

1. react-jss

  1. 文檔地址:https://cssinjs.org/react-jss?v=v10.10.1
  2. 樣式通過 JavaScript 對象來定義,使用對象結(jié)構(gòu)來寫樣式;
  3. 支持動態(tài)樣式組件內(nèi)樣式隔離。??
  4. 樣式的生成發(fā)生在運行時,靈活性高,但在性能上不如靜態(tài)編譯的方案;
  5. 適合復(fù)雜的、需要動態(tài)生成或修改樣式的場景,但可能在某些性能敏感的場景下表現(xiàn)不佳。(jss系列已停止維護,不推薦使用??)
import React from 'react'
import { createUseStyles } from 'react-jss'

const Button = ({children, ...props}) => {
  const class = useStyles(props);
  return (
    <button className={class.myButton}>
        <label className={class.myLabel}>{children}</label>
    </button>
  )
}

const useStyles = createUseStyles({
  myButton: {
    margin: 10,
    padding: props=>props.spacing
  },
  myLabel: props =>({
    height: props.height
  })
})
<Button height={10} spaceing={5}>Submit</Button>

上述代碼將編譯為 ?? ?? ?? ;
但當(dāng)heightspaceing更新時則會重新生成classname,如Button-myButton-1-25??Button-myButton-1-27

<button class="Button-myButton-1-25">
  <label class="Button-myLabel-1-26"> Submit </label>
</button>

<style>
.Button-myButton-1-25 {
  margin: 10px;
  padding: 5px;
}
.Button-myLabel-1-26 {
  heighte: 10px;
}
</style>

2. @stylexjs/stylex

  1. 文檔地址:https://stylexjs.com/docs/learn/styling-ui/defining-styles/
  2. 通過靜態(tài)提取的方式將樣式轉(zhuǎn)化為類名;零運行時消耗。??
  3. 適用于渲染性能要求比較高的應(yīng)用場景,特別是移動端的應(yīng)用。(推薦使用?)
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  button: {
    backgroundColor: 'lightblue',
  }
});

<button {…stylex.props(styles.button)} />
  1. 支持動態(tài)注入(將生成依賴于CSS變量的靜態(tài)樣式,并在運行時設(shè)置該變量的值)。
import * as stylex from "@stylexjs/stylex"
const styles = stylex.create({
  button: (height) => ({
    backgroundColor: 'lightblue',
    height
  })
})

<button {...stylex.props(styles.button(19))} />

上述代碼將編譯為?? ?? ??

<button class="x1jwls1v" style="--height:19" />

<style>
  .x1jwls1v {
    background-color: lightblue;
    height: var(--height, revert);
  }
</style>

Styled Component

Styled-Component也是一類具體的 CSS-in-JS 實現(xiàn)庫,
styled寫法由 styled-component首創(chuàng),使用ES6模版字符串的形式編寫強調(diào)組件級別樣式,在CSS-in-JS也可以作為一個大類。

1. styled-component

  1. styled方法可以在所有組件或任何第三方組件上完美運行,只要組件是通過className屬性獲取樣式;
    ??Note: react-native組件已經(jīng)默認兼容為style

  2. 優(yōu)勢是:有著成熟的生態(tài)系統(tǒng)和社區(qū)支持,許多第三方庫和組件也基于它,適合初次接觸CSS-in-JS的開發(fā)者。 ?

import { styled } from 'styled-components'

const Button = styled.button`
    color: turquoise;
 `

return (
  <Button></Button>
){

 
{/*樣式最終是被編譯成hash類名 ?? ?? ??*/}
(<button {…props} className=“css-hashname”/>)

2. @emotion/styled

  1. 相對于styled-component,@emotion/styled是經(jīng)過@emotion系列拆分后的體積更小
  2. SSR 方面的表現(xiàn)更為出色,支持靜態(tài) CSS 提取,可以減少首次加載時的樣式計算。?
  3. @emption/css首次提出了css模版字符串生成classname的寫法
  • @emotion/styled
    styled-component獨立包

    import styled from '@emotion/styled'
    
  • @emotion/css
    書寫內(nèi)聯(lián)style生成className的基礎(chǔ)包??

    /** @jsxImportSource @emotion/css */
    import { css } from '@emotion/css'
    
    const className = css`
      color: hotpink;
    `
    (<div className={className} />)
    
  • @emotion/react
    react項目專用

    import { jsx, css, Global, ClassNames } from '@emotion/react'
    

twin.macro

通過Babel宏提供簡便語法糖

??twin.macro 集成了 styled-component@emotion/styledtailwindcss
(查看 package.json 依賴)

Note: nextjs14 SWC打包器不支持babel宏,不過最近修復(fù)了這個問題 https://github.com/ben-rogerson/twin.examples/tree/master/next-stitches-typescript

  1. 【 tw=“classname” 】?? className

    可以直接引用tailwind類名 ??

    import tw from "twin.macro";
    
    <Tag tw={"text-red-900"}> antd tag </Tag>
    
    ?? ?? ?? 
    
    (<Tag className="text-red-900"> antd tag </Tag>)
    
  2. 【 css={styleProps} 】?? className

    (<Tag css={{color: "red"}}> antd tag </Tag>)
    
    ?? ?? ??
    
    (<Tag className="css-hashxx-Home"> antd tag </Tag>)
    

    和 【@emotion/css】 混合使用

    import tw from "twin.macro"
    import { css } from "@emotion/css";
    
    <div className="css`font-size:50px; ${tw`text-red-900`}`"></div>
    
  3. **tw【classname】 ?? css **??

    const style = tw`text-red-900`
    ?? ?? ??
    const style = {color:”red”}
    
    (<h1 style={style}>asd</h1>)
    

結(jié)合 Tailwind CSS 的實用性與 CSS-in-JS 的靈活性

  1. 單一的styled-component會疲于手寫CSS樣式;
  2. 單一的tailwind樣式類名會導(dǎo)致代碼很長、閱讀性差;
  3. 各取所長,即做到組件級別的樣式隔離,又能快速定義樣式。
import tw, { styled } from 'twin.macro'

const Input = styled.input`
  color: purple;
  ${tw`border rounded`}
  ${({ hasHover }) => hasHover && tw`hover:border-black`}
`
const Component = () => <Input hasHover />

antd在CSS-in-JS中的實踐

CSS架構(gòu)模式(v4.x)

在V5.0之前的組件樣式設(shè)計使用CSS架構(gòu)模式,便于:

  1. 修改prefixCls
<Button prefixCls="my" />
<style>
 .my-btn {}
</style>

  ?? ?? ??
- <button class="ant-btn" />
+ <button class="my-btn" />
  1. 固定 class 便于覆蓋(包括傀儡class)

    傀儡class:本身無樣式,為了單純覆蓋

  2. 按需引入

    import {Button} from "antd"
    
    ?? ?? ??
    import Button from 'antd/lib/button'
    import 'antd/es/button/style'
    

CSS變量方案

V4.17.0文檔:https://4x.ant.design/docs/react/customize-theme-variable-cn (試驗性)
V5.0文檔:https://ant.design/docs/react/css-variables-cn (融合了CSS-in-JS能力)

CSS變量的優(yōu)勢:

  • 樣式只生成一次
  • 動態(tài)主題只修改變量
  • 多主題只增加變量

CSS變量的劣勢:

  • 瀏覽起兼容性差(如IE)
  • 動態(tài)修改 CSS 變量可能導(dǎo)致性能下降,尤其是在頻繁更改時
  • 可通過cssVar配置來開啟/關(guān)閉CSS變量模式

CSS-in-JS方案

在5.0正式版本中,除了保留之前的CSS架構(gòu)模式外,還帶入了Ant Design獨特的CSS-in-JS 方案(我們稱之為《組件級別的CSS-in-JS》)??
相關(guān)依賴庫:@ant-design/cssinjs

  1. 組件為單位的緩存
    上述雖有plugin自動幫我們引入css了,但是CSS-in-JS可以更好地封裝組件,
    因為樣式直接寫在JS里了,import一個文件即可。

  2. 支持動態(tài)主題/嵌套主題

    在V5.0中ConfigProvider組件引入了theme屬性,外加每個組件都細化了足量的Design Token,帶來了無與倫比的主題自定義能力。文檔鏈接:《定制主題》

<ConfigProvider
  theme={{
    token: {
      // Seed Token,影響范圍大
      colorPrimary: '#00b96b',
      borderRadius: 2,

      // 派生變量,影響范圍小
      colorBgContainer: '#f6ffed',
    },
  }}
>
   <Button type="primary">Theme 2</Button>
</ConfigProvider>
  1. 針對SSR的優(yōu)化
    參考代碼: @ant-design/nextjs-registry

antd-style

文檔鏈接:https://ant-design.github.io/antd-style/zh-CN/guide

antd內(nèi)部使用的@ant-design/cssinjs寫法極其復(fù)雜難懂,這是為了兼容歷史包袱的產(chǎn)物,也為了換得相比 styled-component 和 emotion 都要好很多的性能。

雖然antd內(nèi)部不能像JSS或者Styled Component那樣的寫法,因為職責(zé)和邊界只在于提供高品質(zhì)的基礎(chǔ)組件;但是應(yīng)用層如何使用樣式方案, antd 并不限制。

??為了將 v5 的 token 系統(tǒng)的推行變得更加順利,ant design組織還提供了一個使用antd token系統(tǒng)的最佳cssinjs方案:antd-style。

JSS + css模版字符串

antd-style是基于emotion二次封裝,提供的核心api是createStyles,所以寫法上包含了JSScss模版字符串2種寫法:

import { createStyles } from 'antd-style';

const useStyles = createStyles(({ token, css }) => ({
  // 支持 css object 的寫法
  container: {
    backgroundColor: token.colorBgLayout,
    borderRadius: token.borderRadiusLG,
    maxWidth: 400,
    width: '100%',
    height: 180,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
    marginLeft: 'auto',
    marginRight: 'auto',
  },
  // 也支持通過 css 字符串模板獲得和 普通 css 一致的書寫體驗
  card: css`
    color: ${token.colorTextTertiary};
    box-shadow: ${token.boxShadow};
    &:hover {
      color: ${token.colorTextSecondary};
      box-shadow: ${token.boxShadowSecondary};
    }

    padding: ${token.padding}px;
    border-radius: ${token.borderRadius}px;
    background: ${token.colorBgContainer};
    transition: all 100ms ${token.motionEaseInBack};

    margin-bottom: 8px;
    cursor: pointer;
  `,
}));

export default () => {
  // styles 對象在 useStyles 方法中默認會被緩存,所以不用擔(dān)心 re-render 問題
  const { styles, cx, theme } = useStyles();

  return (
    // 使用 cx 可以組織 className
    <div className={cx('a-simple-create-style-demo-classname', styles.container)}>
      <div className={styles.card}>createStyles Demo</div>
      {/* theme 對象包含了所有的 token 與主題等信息 */}
      <div>當(dāng)前主題模式:{theme.appearance}</div>
    </div>
  );
};

以上,就是CSS in JS目前所有的思想。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內(nèi)容