如何在React中優雅的寫CSS

引言

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

看下這樣的目錄結構:react

├── 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 的做用域污染問題,會產生非預期的頁面樣式渲染結果。webpack

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

comA.css:github

.title {
    color: red;
}
複製代碼

comB.cssweb

.title {
    font-size: 14px;
}
複製代碼

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

.title {
    color: red;
}
.title {
    font-size: 14px;
}
複製代碼

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

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

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

解決 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文檔進入

github.com/MicheleBert… ,能夠發現目前的 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 更具體的配置項參考:www.npmjs.com/package/css…

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 ,可是誰優誰勝沒法武斷。

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