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

Strong opinions are very useful to others.css

Those who were undecided or ambivalent can just adopt your stance.html

But those who disagree can solidify their stance by arguing against yoursreact

鮮明的觀點很是有用。搖擺不定的人能夠省心直接接受你的觀點。不一樣意的人則能夠經過討論更加鞏固本身的觀點。webpack

----Derek Siversgit

前言

不得不認可 Vue 的css解決方式很是天然簡潔,相比之下 css 一直是 React 的痛。 從舊寵 css modules 到 JSS 的各類衍生,到新寵 styled-components。幾十種的解決方式,上百篇的教程和比較,已經說明了一切。你們一直在尋找最好的最適合本身的解決方式。 我試着先回顧一下一路下來用過和沒用過的各類React的css解決方案,最後說說我最愛的方式。固然只要你喜歡,使用普通的 css 或者是 sass 來完成React的樣式是徹底可行的。用本身最喜歡的方式編程是最重要的。github

普通 css 的不足

隨着你們對新開發模式(組件化)下 css 使用的各類反思,我的總結主要有三個:web

  1. 樣式與狀態相關的狀況愈來愈多,須要動態、能直接訪問組件state的css。
  2. 現代web開發已是組件化的天下,而css並非爲組件化而生的語言。
  3. 一切樣式都是全局,產生的各類命名的痛苦,BEM等命名規則能解決一部分問題,但當你使用三方插件時卻沒法避免命名衝突。

Vue 的解決法

<style> /* 全局樣式 */ </style>

<style scoped> /* 本地樣式 */ </style>
複製代碼

一旦加上 scoped 屬性,css 就只做用於所在組件。簡潔漂亮的解決。美中不足的是樣式並不能直接訪問組件狀態,因而乎須要另外規定動態css的語法與此合併使用。編程

回顧 React 的解決法

1. 原生

const textStyles = {
  color: 'white',
  backgroundColor: this.state.bgColor
};

<p style={textStyles}>inline style</p>
複製代碼

原生的解決方式就是inline style,這種在舊式開發上不推崇的css寫法卻很是適合組件化開發。inline style解決了以前提到的三個問題。但相對的,我的以爲不喜歡的地方在於:bootstrap

  1. 發明了一套新的 css-in-js 語法,使用駝峯化名稱等一些規則,須要從新熟悉不說,也沒有自動補完(方便討論下面稱這類寫法jss)
  2. 而且並不支持全部的 css,例如媒體查詢,:before:nth-child等 pseudo selectors
  3. inline 寫法若是直接同行寫影響代碼閱讀,若是提取出來再namespace,比起傳統css要繁瑣
  4. 第三方插件若是隻接受 className 不接受 style 就無法了

因爲1,3只是我的偏好問題,因此以後一批css-in-js庫都堅持了inline和jss,只是致力於解決對css的不徹底支持問題。這些雖然不是個人菜,但都是流行的解決方式:數組

2. Css-in-Js

JSS

專門針對原生方法不徹底支持css的不足,完成的改良版。

// 支持 hover, sass的 &, media query 等。 
const styles = {
  button: {
    fontSize: 12,
    '&:hover': {
      background: 'blue'
    }
  },
  ctaButton: {
    extend: 'button',
    '&:hover': {
      background: color('blue')
        .darken(0.3)
        .hex()
    }
  },
  '@media (min-width: 1024px)': {
    button: {
      width: 200
    }
  }
}
複製代碼

JSS 是一個底層庫,要在 React 中使用能夠用 React-JSS, Styled-JSS 等。選擇多樣,是此類解決法裏不錯的一個選擇。

Radium

import Radium from 'radium';

const Button = () => (
    <button style={styles.base}> {this.props.children} </button>;
)

var styles = {
  red: {
    backgroundColor: 'red'
  }
};

Button = Radium(Button);
複製代碼

多個樣式使用數組方便合併

<button style={[styles.base,styles.primary]}>
    {this.props.children}
</button>
複製代碼

使用了HOC的方式注入樣式,能夠方便傳入各類配置

Radium(config)(App)
複製代碼

Aphrodite

import React, { Component } from 'react';
import { StyleSheet, css } from 'aphrodite';

const Button = () => (
    <span className={css(styles.red)}> This is red. </span>
)

const styles = StyleSheet.create({
    red: {
        backgroundColor: 'red'
    },
});
複製代碼

並非全部人都接受jss的書寫。因此另外一種解決方式是繼續使用 css 的同時,解決樣式的scope 問題,使得樣式只做用於import它的組件。這類解決方法目前就一家

3. Css Modules

Css Modules 並非React專用解決方法,適用於全部使用 webpack 等打包工具的開發環境。以 webpack 爲例,在 css-loader 的 options 裏打開modules:true 選項便可使用 Css Modules。
通常配置以下

{
  loader: "css-loader",
  options: {
    importLoaders: 1,
    modules: true,
    localIdentName: "[name]__[local]___[hash:base64:5]"  // 爲了生成類名不是純隨機
  },
},
複製代碼

使用以下

import styles from './table.css';

    render () {
        return <div className={styles.table}> <div className={styles.row}> <div className={styles.cell}>A0</div> <div className={styles.cell}>B0</div> </div> </div>;
    }
複製代碼
/* table.css */
.table {}
.row {}
.cell {}
複製代碼

在解決了 scoped 的同時留下些許遺憾:

  1. class名必須是駝峯形式,不然不能正常在js裏使用 styles.table 來引用
  2. 因爲css模塊化是默認,當你但願使用正常的全局css時,須要經過:local:global 切換,不方便
  3. 全部的 className 都必須使用 {style.className} 的形式

這個解決方法能夠照常寫css是一大優點,不過我對 2,3 比較不能容忍。一個輕量級的 babel-plugin-react-css-modules庫提出瞭解決法,你能夠照常寫 'table-size' 之類帶橫槓的類名,在js里正常書寫字符串類名,惟一的區別在於使用 styleName 關鍵字代替 className, 以上例,結果以下

import './table.css';

render () {
        return <div styleName='table'> <div styleName='row'> <div styleName='cell'>A0</div> <div styleName='cell'>B0</div> </div> </div>;
    }
複製代碼

使用styleName這一新關鍵字,甚至連局部css和全局css的區分也迎刃而解了。

<div className='global-css' styleName='local-module'></div>
複製代碼

使用時.babelrc配置:

{
  "plugins": [
    ["react-css-modules", {
      // options
    }]
  ]
}
複製代碼

這一解決法已經很接近個人喜愛了,不過使用 styleName 遇到三方UI庫該怎麼辦呢?

順帶一提,目前 create-react-app 還不支持 Css Modules,但處於 beta 的 create-react-app v2 已經支持。使用方法爲一概將css文件命名爲 XXX.modules.css, 以上例,即爲 table.modules.css, 便可使用。這一解決法的優雅在於,全局的css能夠正常使用,只有帶.modules.css後綴的纔會被modules化。

Css Modules還有一大缺憾:和Vue的解決同樣,由於css寫在css文件,沒法處理動態css。

4. Css-in-Js 新浪潮

有不少人並不買JSS的賬(我算一個),Vue 的解決方式也算一個啓發,因而新的庫嘗試了使用 ES6 的模板字符串,在js文件裏寫純粹的css。

` .table { background: #333; color: rebeccapurple; } `
複製代碼

這很是天然地解決了前面提到的原生方法缺陷之2:不支持全部css語法。這類解決法中最有名的是 styled-components,相似的還有 Emotion。起初這一方法的一大不即是編輯器不能格式化,lint和自動補完js中的css,但如今基本每一個流行編輯器都能找到相應的插件解決這一問題。

styled-components

import styled from 'styled-components';

// `` 和 () 同樣能夠做爲js裏做爲函數接受參數的標誌,這個作法相似於HOC,包裹一層css到h1上生成新組件Title
const Title = styled.h1` font-size: 1.5em; text-align: center; color: palevioletred; `;

// 在充分使用css所有功能的同時,很是方便的實現動態css, 甚至能夠直接調用props!
const Wrapper = styled.section` padding: 4em; background: ${props => props.bgColor}; `;

const App = () => (
    <Wrapper bgColor='papayawhi'> <Title>Hello World, this is my first styled component!</Title> </Wrapper>
)
複製代碼

值得注意的是支持對其餘元素引用,且語法至關天然:

const Link = styled.a` padding: 5px 10px; background: papayawhip; color: palevioletred; `;

const Icon = styled.svg` transition: fill 0.25s; width: 48px; height: 48px; ${Link}:hover & { fill: rebeccapurple; } `;
複製代碼

而且能方便的給暴露className props的三方UI庫上樣式:

const StyledButton = styled(Button)` ... `
複製代碼

有人喜歡給每一個須要樣式的標籤重命名一個更有意義的名稱的作法,但也有人以爲重命名很是繁瑣。這類人能夠試試Emotion

Emotion

import styled, { css } from 'react-emotion'

const Container = styled('div')` background: #333; `
const myStyle = css` color: rebeccapurple; `
const app = () => (
  <Container> <p className={myStyle}>Hello World</p> </Container>
)
複製代碼

Emotion 支持 styled-components 的樣式注入方式,同時也能夠用 css() 關鍵字直接注入。同時也支持JSS和& :hover等Sass語法,例如:

// sass
<div className={css` background-color: hotpink; &:hover { color: ${color}; } `} >
// JSS 
<div className={css({ backgroundColor: 'hotpink', '&:hover': { color: 'lightgreen' } })} >
複製代碼

以前沒注意,Emotion 容許直接寫子元素樣式!

import { css } from 'emotion'

const paragraph = css` color: turquoise; a { border-bottom: 1px solid currentColor; } `
render(
  <p className={paragraph}> Some text. <a> A link with a bottom border. </a> </p>
)
複製代碼

若是使用 babel-plugin-emotion,你甚至能夠直接使用 css 做爲 props:

<div css={` color: blue; font-size: ${props.fontSize}px; &:hover { color: green; } & .some-class { font-size: 20px; } `} >
複製代碼

很是簡潔也獨具亮點,且提供了符合各類胃口的解決方式。

Glamorous

Glamorous 和前兩個庫有不少相似之處,最大的不一樣是它堅守了JSS的陣線。不支持模板字符串寫純css。 Glamorous 基礎用法有三種選擇:

import 
import glamor from 'glamorous'
// className 注入
const styles = glamor.css({
  fontSize: 20,
  textAlign: 'center',
})

<div
  className={styles}
/>

// 2. 類styled components
const MyStyledDiv = glamor.div({margin: 1, fontSize: 1, padding: 1})

// 3. 使用自帶組件,接受樣式名和css爲props
const { Div } = glamor

<Div
  fontSize={20}
  textAlign="center"
  css={{
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center'
  }}
>
  Hello world!
</Div>

// 4. 以及混用
const MyStyledDiv = glamor.div({margin: 1, fontSize: 1, padding: 1})
const myCustomGlamorStyles = glamor.css({fontSize: 2})
<MyStyledDiv className={`${myCustomGlamorStyles} custom-class`} />
複製代碼

最後談談,個人最愛

1. styled-jsx

Next.js 的 zeit 出品,必屬精品。

2. 意想不到的解決方案 (我的大愛)

tachyons

tailwind.css

兩個css庫,與 bootstrap 走上徹底不一樣的路線,所謂的「原子類」。寫小項目和demo我基本上第一件事就是

yarn add tachyons

篇幅意想不到變得太長了,在下篇我會展開講解這兩個我的偏心的解決方法。本篇對現行的流行解決法作了個小概括。方便你們查詢和選擇。在本篇裏我用的最多的仍是styled-components,但如今已經切換到styled-jsx了。 以後最想嘗試的應該是Emotion。說到底沒有孰優孰劣,更多的是我的喜愛。但願這篇概括對你們有幫助。不足之處,也請你們能留言指出,互相學習,謝謝!

中篇:從10種如今流行的 CSS 解決方案談談個人最愛 (中)

相關文章
相關標籤/搜索