React拾遺:從10種如今流行的 CSS 解決方案談談個人最愛 (下)

If you are not paying for the product, you are the product.
當一個商品是「免費」的,那每每你成了商品。css

終於有時間靜下心學點東西,把這個系列最後一篇填上。 中篇 介紹了 tachyons, 本篇介紹我的的最愛,沒有之一:styled-jsx。html

選擇標準

在打算使用一個新 css 框架前,須要好好想清楚它是否在你的全部使用場景裏圓滿完成任務,我總結了幾點:vue

  • 是否解決了React開發的痛點:局部css,動態css?
  • 是否支持全部css甚至是sass用法?僞類,嵌套,動畫,媒體查詢?
  • 是否兼容你須要使用的第三方UI庫?
  • 是否能和純css,或者是其餘css框架很好共存,以備遇到特殊狀況能夠有方案B?
  • 性能?大小?

styled-jsx

zeit 的一系列產品從 now,到 next.js,我算是一個腦殘粉。簡潔好用是我對 zeit 的項目的印象。並且一套庫自成系統,styled-jsx 和 next.js 完美兼容。node

1. 基礎用法

styled-jsx 歸納第一印象就是 React css 的 vue 解決。
yarn add styled-jsx 安裝後,不用import,而是一個babel插件,.babelrc配置:react

{
  "plugins": [
    "styled-jsx/babel"
  ]
}
複製代碼

而後就直接用了git

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>
)
複製代碼

會被轉化成github

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-jsxapi

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

兩個字,插件。

包羅萬象。以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個細小缺點

  1. 和CRA整合時,css-in-js 的解決方式不會熱加載,每次都刷新頁面。
  2. 和CRA整合時,因爲是babel插件,須要eject或者使用react-app-rewired來加入 babel plugin 的設置。

以前有小夥伴留言表示有坑(謝謝,看到這樣的留言以爲寫blog的決定太對了)

一樣以爲styled-jsx很是好,最近一直在用。
但目前仍是有些不成熟的地方,主要是:
一、局部樣式沒法支持第三方組件標籤,只能支持普通html標籤
二、對stylelint缺少支持,官方出的stylelint插件只是一個demo。在vscode中用stylelint插件時也是有很大的坑。
複製代碼

我的以爲1的場景已經在上面討論了,能夠兼容。2的話,本人上個項目裏裸prettier,因此沒試過那個插件,在這裏提一下。

總之目前用過一次,我很喜歡,也很滿意。但願你們也找到本身喜歡的一個或者是一套解決方法。

最近面試,明白了本身有許多須要重點研究的課題。看了些 PWA 相關的文和視頻,已經上船了。一句話:PWA 是將來,並且是近在眼前的將來。以後打算分享一下本身的學習筆記。

我寫的其餘專欄文章列表 傳送門

相關文章
相關標籤/搜索