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


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

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

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

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>;
}
  • ``的位置可以按喜好自定,樣式總是作用於組件的所有元素
  • 樣式只作用於本組件,不影響全局也不影響子組件
  • 實現方式大致是給組件內所有標簽自動加上了一個獨特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>
)

同個組件里可以寫無限個``標簽,這里的最佳實踐是將靜態的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非常好,最近一直在用。
但目前還是有些不成熟的地方,主要是:
1、局部樣式無法支持第三方組件標簽,只能支持普通html標簽
2、對stylelint缺乏支持,官方出的stylelint插件只是一個demo。在vscode中用stylelint插件時也是有很大的坑。

個人覺得1的場景已經在上面討論了,可以兼容。2的話,本人上個項目里裸prettier,所以沒試過那個插件,在這里提一下。

總之目前用過一次,我很喜歡,也很滿意。希望大家也找到自己喜歡的一個或者是一套解決方法。

最近面試,明白了自己有許多需要重點研究的課題。看了些 PWA 相關的文和視頻,已經上船了。一句話:PWA 是未來,而且是近在眼前的未來。之后打算分享一下自己的學習筆記。


免責聲明!

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



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