目錄
- 前言
- JSS (JavaScript Style Sheets)
- react-jss
- @stylexjs/stylex
- Styled Component
- styled-component
- @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
- 文檔地址:https://cssinjs.org/react-jss?v=v10.10.1
- 樣式通過 JavaScript 對象來定義,使用對象結(jié)構(gòu)來寫樣式;
- 支持動態(tài)樣式和組件內(nèi)樣式隔離。??
- 樣式的生成發(fā)生在運行時,靈活性高,但在性能上不如靜態(tài)編譯的方案;
- 適合復(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)height
或spaceing
更新時則會重新生成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
- 文檔地址:https://stylexjs.com/docs/learn/styling-ui/defining-styles/
- 通過靜態(tài)提取的方式將樣式轉(zhuǎn)化為類名;零運行時消耗。??
- 適用于渲染性能要求比較高的應(yīng)用場景,特別是移動端的應(yīng)用。(推薦使用?)
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
button: {
backgroundColor: 'lightblue',
}
});
<button {…stylex.props(styles.button)} />
- 支持動態(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
styled
方法可以在所有組件或任何第三方組件上完美運行,只要組件是通過className
屬性獲取樣式;
??Note: react-native組件已經(jīng)默認兼容為style
優(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
- 相對于
styled-component
,@emotion/styled
是經(jīng)過@emotion
系列拆分后的體積更小 - 在 SSR 方面的表現(xiàn)更為出色,支持靜態(tài) CSS 提取,可以減少首次加載時的樣式計算。?
-
@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/styled
和tailwindcss
(查看 package.json 依賴)Note: nextjs14 SWC打包器不支持babel宏,不過最近修復(fù)了這個問題 https://github.com/ben-rogerson/twin.examples/tree/master/next-stitches-typescript
-
【 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>)
-
【 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>
-
**tw【classname】 ?? css **??
const style = tw`text-red-900` ?? ?? ?? const style = {color:”red”} (<h1 style={style}>asd</h1>)
結(jié)合 Tailwind CSS 的實用性與 CSS-in-JS 的靈活性
- 單一的
styled-component
會疲于手寫CSS樣式; - 單一的
tailwind
樣式類名會導(dǎo)致代碼很長、閱讀性差; - 各取所長,即做到組件級別的樣式隔離,又能快速定義樣式。
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)模式,便于:
- 修改
prefixCls
<Button prefixCls="my" />
<style>
.my-btn {}
</style>
?? ?? ??
- <button class="ant-btn" />
+ <button class="my-btn" />
-
固定 class 便于覆蓋(包括傀儡class)
傀儡class:本身無樣式,為了單純覆蓋
-
按需引入
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
組件為單位的緩存
上述雖有plugin自動幫我們引入css了,但是CSS-in-JS可以更好地封裝組件,
因為樣式直接寫在JS里了,import一個文件即可。-
支持動態(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>
-
針對SSR的優(yōu)化
參考代碼: @ant-design/nextjs-registry
antd-style
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
,所以寫法上包含了JSS和css模版字符串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目前所有的思想。