React拾遺:從10種現在流行的 CSS 解決方案談談我的最愛 (上)


最近寫React一直在糾結樣式的問題,今天找了篇看起來很不錯的文章,認真讀三遍先...

原文鏈接:https://juejin.im/post/5b39e63ae51d4562aa017c81

React拾遺:從10種現在流行的 CSS 解決方案談談我的最愛 (上)

Strong opinions are very useful to others.

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

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

鮮明的觀點非常有用。搖擺不定的人可以省心直接接受你的觀點。不同意的人則可以通過討論更加鞏固自己的觀點。

前言

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

普通 css 的不足

隨着大家對新開發模式(組件化)下 css 使用的各種反思,個人總結主要有三個:

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

Vue 的解決法

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

<style scoped>
/* 本地樣式 */
</style>

一旦加上 scoped 屬性,css就只能作用於組件當中。簡單漂亮的解決(Even You 真的666)。美中不足的是樣式並不能直接訪問組件的狀態,於是需要另外規定動態的css語法與此合並使用。

回顧 React 的解決法

1. 原生 inline style (對於我個人來說,這種我偶爾還是會用用)

const textStyles = {
	color: 'White',
	backgroundColor: this.state.bgColor;
}
<p style={textStyles}>inline style</p>

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

  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'
    },
});

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="papaywwhi">
		<Title>Hello World, this is my first styled componenet!</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。說到底沒有孰優孰劣,更多的是個人喜好。希望這篇歸納對大家有幫助。不足之處,也請大家能留言指出,互相學習,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM