If you are not paying for the product, you are the product.
當一個商品是“免費”的,那往往你成了商品。
終於有時間靜下心學點東西,把這個系列最後一篇填上。 中篇 介紹了 tachyons, 本篇介紹個人的最愛,沒有之一:styled-jsx。
選擇標準
在打算使用一個新 css 框架前,需要好好想清楚它是否在你的所有使用場景裡圓滿完成任務,我總結了幾點:
- 是否解決了React開發的痛點:區域性css,動態css?
- 是否支援所有css甚至是sass用法?偽類,巢狀,動畫,媒體查詢?
- 是否相容你需要使用的第三方UI庫?
- 是否能和純css,或者是其他css框架很好共存,以備遇到特殊情況可以有方案B?
- 效能?大小?
styled-jsx
zeit 的一系列產品從 now,到 next.js,我算是一個腦殘粉。簡潔好用是我對 zeit 的專案的印象。而且一套庫自成系統,styled-jsx 和 next.js 完美相容。
1. 基礎用法
styled-jsx 概括第一印象就是 React css 的 vue 解決。
yarn add styled-jsx
安裝後,不用import
,而是一個babel外掛,.babelrc
配置:
{
"plugins": [
"styled-jsx/babel"
]
}
複製程式碼
然後就直接用了
render () {
return <div className='table'>
<div className='row'>
<div className='cell'>A0</div>
<div className='cell'>B0</div>
</div>
<style jsx>{`
.table {
margin: 10px;
}
.row {
border: 1px solid black;
}
.cell {
color: red;
}
`}</style>
</div>;
}
複製程式碼
<style>
的位置可以按喜好自定,樣式總是作用於元件的所有元素- 樣式只作用於本元件,不影響全域性也不影響子元件
- 實現方式大致是給元件內所有標籤自動加上了一個獨特
className
例如
const App = () => (
<div>
<p>只有這個p會被上樣式</p>
<style jsx>{`
p {
color: red;
}
`}</style>
</div>
)
複製程式碼
會被轉化成
import _JSXStyle from 'styled-jsx/style'
const App = () => (
<div className='jsx-123'>
<p className='jsx-123'>只有這個p會被上樣式</p>
<_JSXStyle styleId='123' css={`p.jsx-123 {color: red;}`} />
</div>
)
複製程式碼
從實現到原理,對比vue是不是非常像呢?如果你不喜歡將樣式寫在 render 裡,styled-jsx 提供了一個 css
的工具函式:
import css from 'styled-jsx/css'
export default () => (
<div>
<button>styled-jsx</button>
<style jsx>{button}</style>
</div>
)
const button = css`button { color: hotpink; }`
複製程式碼
非常完美的css解決方案。 下面針對“選擇標準”裡提到的各個方面考察一下 styled-jsx
2. 動態css
和 styled-components,Motion 等模板字串的解決方案一樣,動態css輕而易舉
export default (props) => (
<div>
<button>styled-jsx</button>
<style jsx>{
`button { color: ${props.color}; }`
}</style>
</div>
)
複製程式碼
同個元件裡可以寫無限個<style>
標籤,這裡的最佳實踐是將靜態的css放一個標籤,動態的放另一個,每次渲染時只有動態的重新計算和渲染
const Button = (props) => (
<button>
{ props.children }
<style jsx>{`
button {
color: #999;
display: inline-block;
font-size: 2em;
}
`}</style>
<style jsx>{`
button {
padding: ${ 'large' in props ? '50' : '20' }px;
background: ${props.theme.background};
}
`}</style>
</button>
)
複製程式碼
兩點注意:
- 只有寫在 render 函式裡的style是動態的,所以動態css不能如第二例那樣提取出來
- 原聲解決方法的 style props的樣式會覆蓋 styled-jsx 的樣式
3. 如何使用Sass
兩個字,外掛。
- styled-jsx-plugin-sass
- styled-jsx-plugin-postcss
- styled-jsx-plugin-stylelint
- styled-jsx-plugin-less
- styled-jsx-plugin-stylus
一應俱全。以Sass為例:
yarn add -D node-sass styled-jsx-plugin-sass
複製程式碼
.babelrc
配置
{
"plugins": [
[
"styled-jsx/babel",
{ "plugins": ["styled-jsx-plugin-sass"] }
]
]
}
複製程式碼
即可使用Sass。
4. 全域性css
如同 Vue 以 scoped
為關鍵字,styled-jsx 以 global
為關鍵字。
export default () => (
<div>
<style jsx global>{`
body {
background: red
}
`}</style>
</div>
)
複製程式碼
全域性樣式註明 global 即可。
有極少情況(比如傳一個全域性類給三方庫)需要使單個選擇器全域性化,語法類似 css-module
div :global(.react-select) {
color: red
}
複製程式碼
5. 三方UI庫的支援
相對有點繁瑣,思想是取得styled-jsx轉化過後的類名,注入到三方庫的className props裡,這樣即解決了支援,又保全了區域性css,程式碼如下
import Link from 'react-router-dom' // 例子,給Link元件新增樣式
const scoped = resolveScopedStyles(
<scope>
<style jsx>{'.link { color: green }'}</style>
</scope>
)
const App = ({ children }) => (
<div>
{children}
<Link to="/about" className={`link ${scoped.className}`}>
About
</Link>
{scoped.styles}
</div>
);
function resolveScopedStyles(scope) {
return {
className: scope.props.className, //就是被styled-jsx新增的獨特className
styles: scope.props.children //就是style,注入到App元件中
}
}
複製程式碼
當然,如果你不介意區域性不區域性,可以使用上面提到的:global()
語法
// 比如Form元件有類名form-item
export default () => (
<div>
<Form />
<style jsx>{`
div > :global(.form-item) {
color: red
}
`}</style>
</div>
)
複製程式碼
6. 語法高亮與補完
我使用VSCode:
其他的見 github 上 readme。
7. 大小?效能?
- 可使用所有css語法
- 3kb的gzip大小
- 瀏覽器的prefix自動加了
- 有source-map 支援,方便debug
- 據作者說效能也很高
8. style-jsx vs styled-components
謝謝ziven27提議,我試著說說和現在最流行的 styled-components 庫的區別。(方便討論,“前者”代指style-jsx,“後者”代指style-components)
- 最大區別在於前者樣式作用於整個元件,後者的樣式只作用於所包裹的元素。打個比方就如同vue和inline css的區別。但其實只要使用sass的
&
巢狀,後者也是可以包裹元件最外層元素然後將其他元素樣式統統寫入的。雖然感覺不是後者的初衷。 - 後者與提供
className
props的三方庫完美整合(比前者簡潔太多):
const RedButton = styled(Button)`background: red;`
複製程式碼
但對不提供className
props的三方庫一籌莫展(當然這種情況不太出現)。
- 後者由於使用了HOC模式,與其他所有HOC一樣要處理ref的問題(包裹層的ref不同於原本元素的ref),多一事。
- 個人覺得前者的規則簡潔明瞭(css怎麼寫就怎麼寫),後者有不少“魔術”的部分(比如props,theme),也引入了很多高階用法如
.extends
樣式繼承以及.attrs
封裝公用props。
這裡提到一個話題,到底是“魔術”好呢,還是樸實好呢?舉個例子,比如theme(主題):
// styled-components 使用Provider提供主題,主題可以是樣式,也可以是函式
const Button = styled.button`
color: ${props => props.theme.fg};
border: 2px solid ${props => props.theme.fg};
background: ${props => props.theme.bg};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
`;
// 主題的樣式
const theme = {
fg: 'palevioletred',
bg: 'white'
};
// 換前景色和背景色的函式
const invertTheme = ({ fg, bg }) => ({
fg: bg,
bg: fg
});
render(
<ThemeProvider theme={theme}>
<div>
<Button>Default Theme</Button>
<ThemeProvider theme={invertTheme}>
<Button>Inverted Theme</Button>
</ThemeProvider>
</div>
</ThemeProvider>
);
複製程式碼
甚至還提供了一個HOC,在styled-components外使用theme
import { withTheme } from 'styled-components'
複製程式碼
強大不強大?對比之下,styled-jsx沒有任何與theme相關的api,只是樸樸實實地靠props傳遞達成主題:
// styled-jsx
import { colors, spacing } from '../theme'
import { invertColor } from '../theme/utils'
const Button = ({ children }) => (
<button>
{ children }
<style jsx>{`
button {
padding: ${ spacing.medium };
background: ${ colors.primary };
color: ${ invertColor(colors.primary) };
}
`}</style>
</button>
)
複製程式碼
這個問題要看應用場景和個人喜好吧,我屬於不喜歡過多“魔術”,愛簡單api(哪怕多點程式碼)的那派。大家有啥想法和指正,多多留言。
最後
有2個細小缺點
- 和CRA整合時,css-in-js 的解決方式不會熱載入,每次都重新整理頁面。
- 和CRA整合時,由於是babel外掛,需要eject或者使用
react-app-rewired
來加入 babel plugin 的設定。
之前有小夥伴留言表示有坑(謝謝,看到這樣的留言覺得寫blog的決定太對了)
同樣覺得styled-jsx非常好,最近一直在用。
但目前還是有些不成熟的地方,主要是:
1、區域性樣式無法支援第三方元件標籤,只能支援普通html標籤
2、對stylelint缺乏支援,官方出的stylelint外掛只是一個demo。在vscode中用stylelint外掛時也是有很大的坑。
複製程式碼
個人覺得1的場景已經在上面討論了,可以相容。2的話,本人上個專案裡裸prettier,所以沒試過那個外掛,在這裡提一下。
總之目前用過一次,我很喜歡,也很滿意。希望大家也找到自己喜歡的一個或者是一套解決方法。
最近面試,明白了自己有許多需要重點研究的課題。看了些 PWA 相關的文和視訊,已經上船了。一句話:PWA 是未來,而且是近在眼前的未來。之後打算分享一下自己的學習筆記。
我寫的其他專欄文章列表 傳送門。