如何在 React 中優雅的寫 CSS

本文首發於政採雲前端團隊博客: 如何在 React 中優雅的寫 CSS

引言

問題:CSS 文件分離 != CSS 做用域隔離css

看下這樣的目錄結構:前端

├── src                                  
│   ├──......                            # 公共組件目錄
│   ├── components                       # 組件
│   │   └──comA                          # 組件A
│   │       ├──comA.js                     
│   │       ├──comA.css                      
│   │       └── index.js                  
│   │   └──comB                          # 組件B
│   │       ├──comB.js                     
│   │       ├──comB.css                      
│   │       └── index.js                  
│   ├── routes                           # 頁面模塊                  
│   │   └── modulesA                     # 模塊A
│   │       ├──pageA.js                  # pageA JS 代碼
│   │       ├──pageA.css                 # pageA CSS 代碼

看目錄結構清晰明瞭,因爲「 CSS 文件分離 != CSS 做用域隔離」這樣的機制,若是咱們不經過一些工具或規範來解決 CSS 的做用域污染問題,會產生非預期的頁面樣式渲染結果。react

假設咱們在組件 A 和組件 B import 引入 comA.css 和 comB.css。webpack

comA.cssgit

.title {
    color: red;
}

comB.cssgithub

.title {
    font-size: 14px;
}

最後打包出來的結果爲:web

.title {
    color: red;
}
.title {
    font-size: 14px;
}

咱們但願,comA.css 二者互不影響,能夠發現,雖然 A、B 兩個組件分別只引用了本身的 CSS 文件,可是 CSS 並無隔離,兩個 CSS 文件是相互影響的!npm

隨着 SPA 的流行,js 能夠組件化,按需加載(路由按需加載、組件的 CSS 和 JS 都按需加載),這種狀況下 CSS 做用域污染的問題被放大,CSS 被按需加載後因爲 CSS 全局污染的問題,在加載出其餘一部分代碼後,可能致使現有的頁面上會出現詭異的樣式變更。這樣的問題加大了發佈的風險以及 debugger 的成本。後端

小編我從寫 Vue 到寫 React,Vue 的 scoped 完美的解決了 CSS 的做用域問題,那麼 React 如何解決 CSS 的做用域問題呢?sass

解決 React 的 CSS 做用域污染方案:

  • 方案一:namespaces
  • 方案二:CSS in JS
  • 方案三:CSS Modules

方案一:namespaces

利用約定好的命名來隔離 CSS 的做用域

comA.css

.comA .title {
    color: red;
}
.comA .……{
    ……
}

comB.css

.comB .title {
    font-size: 14px;
}
.comB .……{
    ……
}

嗯,用 CSS 寫命名空間寫起來貌似有點累。

沒事咱們有 CSS 預處理器,利用 less、sass、stylus 等預處理器,代碼依然簡潔。

A.less

.comA {
    .title {
        color: red;
    }
    
    .…… {
        ……
    }
}

B.less

.comB {
    .title {
        font-size: 14px;
    }
    
    .…… {
        ……
    }
}

貌似很完美解決了 CSS 的做用域問題,可是問題來了,假設 AB 組件是嵌套組件。

那麼最後的渲染 DOM 結構爲:

<div class="comA">
    <h1 class="title">組件A的title</h1>
    <div class="comB">
        <h1 class="title">組件組件的title</h1>
    </div>
</div>

comA 的樣式又成功做用在了組件 B 上。

不要緊,還有解,全部的 class 名以命名空間爲前綴。

<div class="comA">
    <h1 class="comA__title">組件A的title</h1>
    <div class="comB">
        <h1 class="comB__title">組件組件的title</h1>
    </div>
</div>

A.less

.comA {
    &__title {
        color: red;
    }
}

B.less

.comB {
    &__title {
        font-size: 14px;
    }
}

若是,咱們的樣式還遵循 BEM (Block, Element, Modifier) 規範,那麼,樣式名簡直不要太長!可是問題確實也解決了,但約定畢竟是約定,靠約定和自覺來解決問題畢竟不是好方法,在多人維護的業務代碼中這種約定來解決 CSS 污染問題也變得很難。

方案二:CSS in JS

使用 JS 語言寫 CSS,也是 React 官方有推薦的一種方式。

從React文檔進入

https://github.com/MicheleBertoli/css-in-js ,能夠發現目前的 CSS in JS 的第三方庫有60餘種。

看兩個比較大衆的庫:

  • reactCSS
  • styled-components

reactCSS

支持 React、Redux、React Native、autoprefixed、Hover、僞元素和媒體查詢

看下官網文檔 :

const styles = reactCSS({
  'default': {
    card: {
      background: '#fff',
      boxShadow: '0 2px 4px rgba(0,0,0,.15)',
    },
  },
  'zIndex-2': {
    card: {
      boxShadow: '0 4px 8px rgba(0,0,0,.15)',
    },
  },
}, {
  'zIndex-2': props.zIndex === 2,
})
class Component extends React.Component {
  render() {
    const styles = reactCSS({
      'default': {
        card: {
          background: '#fff',
          boxShadow: '0 2px 4px rgba(0,0,0,.15)',
        },
        title: {
          fontSize: '2.8rem',
          color: this.props.color,
        },
      },
    })
    return (
      <div style={ styles.card }>
        <div style={ styles.title }>
          { this.props.title }
        </div>
        { this.props.children }
      </div>
    )
  }
}

能夠看出,CSS 都轉化成了 JS 的寫法,雖然沒有學習成本,可是這種轉變仍是有一絲不適。

styled-components

styled-components,目前社區裏最受歡迎的一款 CSS in JS 方案
const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;
  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${props => props.primary && css`
    background: white;
    color: palevioletred;
  `}
`
render(
  <div>
    <Button
      href="https://github.com/styled-components/styled-components"
      target="_blank"
      rel="noopener"
      primary
    >
      GitHub
    </Button>
    <Button as={Link} href="/docs" prefetch>
      Documentation
    </Button>
  </div>
)

與 reactCSS 不一樣,styled-components 使用了模板字符串,寫法更接近 CSS 的寫法。

方案三:CSS Modules

利用 webpack 等構建工具使 class 做用域爲局部。

CSS 依然是仍是 CSS

例如 webpack ,配置 css-loader 的 options modules: true。

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: 'css-loader',
        options: {
          modules: true,
        },
      },
    ],
  },
};

modules 更具體的配置項參考:https://www.npmjs.com/package/css-loader

loader 會用惟一的標識符 (identifier) 來替換局部選擇器。所選擇的惟一標識符以模塊形式暴露出去。

示例:

webpack css-loader options

options: {
  ...,
  modules: {
    mode: 'local',
    // 樣式名規則配置
    localIdentName: '[name]__[local]--[hash:base64:5]',
  },
},
...

App.js

...
import styles from "./App.css";
...
<div>
  <header className={styles["header__wrapper"]}>
    <h1 className={styles["title"]}>標題</h1>
    <div className={styles["sub-title"]}>描述</div>
  </header>
</div>

App.css

.header__wrapper {
  text-align: center;
}

.title {
  color: gray;
  font-size: 34px;
  font-weight: bold;
}

.sub-title {
  color: green;
  font-size: 16px;
}

編譯後端的 CSS,classname 增長了 hash 值。

.App__header__wrapper--TW7BP {
  text-align: center;
}

.App__title--2qYnk {
  color: gray;
  font-size: 34px;
  font-weight: bold;
}

.App__sub-title--3k88A {
  color: green;
  font-size: 16px;
}

總結

(1)若是是 ui 組件庫中使用

建議使用 namespaces 方案

緣由:

  • ui 組件庫維護人員基本固定,遵照約定的規範較爲容易,可經過約定規範來解決不一樣組件 CSS 相互影響問題
  • 因爲 ui 組件庫會應用於整個公司的產品,在真正的業務場景中,雖然不建議,可是可能沒法避免須要覆蓋組件樣式的特殊場景,如使用其餘兩種方式,不能支持組件樣式覆蓋

(2)若是是業務代碼/業務組件中使用

CSS in JS / CSS Modules

業務代碼維護人員較多且不固定、代碼水平不一致,只經過規範來約束不靠譜,沒法保證開發人員嚴格遵照規範,不能根治 CSS 交叉影響問題,可是從 debug 角度考慮,建議組件外層都添加一個 namespaces 方面定位組件。而後加之 CSS in JS 或 CSS Modules 方案來解決 CSS 交叉影響問題。

CSS in JS 和 CSS Modules 誰優誰勝?

CSS Modules 會比 CSS in JS 的侵入性更小,CSS in JS 能夠和 JS 共享變量,但我的更喜歡 CSS Modules ,可是誰優誰勝沒法武斷。

  • 若是你的團隊尚未使用這任一技術,須要考慮的是團隊成員的感覺
  • 若是已經在使用其中某一種方案,保持一致性便可,相信並這樣走下去

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索