如何在 React 中優(yōu)雅的寫(xiě) CSS
本文首發(fā)于政采云前端團(tuán)隊(duì)博客:如何在 React 中優(yōu)雅的寫(xiě) CSS
https://www.zoo.team/article/react-css

引言
問(wèn)題:CSS 文件分離 != ?CSS 作用域隔離
看下這樣的目錄結(jié)構(gòu):
├── src? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
│? ?├──......? ? ? ? ? ? ? ? ? ?# 公共組件目錄
│? ?├── components? ? ? ? ? ? ? # 組件
│? ?│? ?└──comA? ? ? ? ? ? ? ? ?# 組件A
│? ?│? ? ? ?├──comA.js? ? ? ? ? ? ? ? ? ? ?
│? ?│? ? ? ?├──comA.css? ? ? ? ? ? ? ? ? ? ??
│? ?│? ? ? ?└── index.js? ? ? ? ? ? ? ? ??
│? ?│? ?└──comB? ? ? ? ? ? ? ? ?# 組件B
│? ?│? ? ? ?├──comB.js? ? ? ? ? ? ? ? ? ? ?
│? ?│? ? ? ?├──comB.css? ? ? ? ? ? ? ? ? ? ??
│? ?│? ? ? ?└── index.js? ? ? ? ? ? ? ? ??
│? ?├── routes? ? ? ? ? ? ? ? ? # 頁(yè)面模塊? ? ? ? ? ? ? ? ??
│? ?│? ?└── modulesA? ? ? ? ? ? # 模塊A
│? ?│? ? ? ?├──pageA.js? ? ? ? ?# pageA JS 代碼
│? ?│? ? ? ?├──pageA.css? ? ? ? # pageA CSS 代碼
看目錄結(jié)構(gòu)清晰明了,由于“ CSS 文件分離 != ?CSS 作用域隔離”這樣的機(jī)制,如果我們不通過(guò)一些工具或規(guī)范來(lái)解決 CSS 的作用域污染問(wèn)題,會(huì)產(chǎn)生非預(yù)期的頁(yè)面樣式渲染結(jié)果。
假設(shè)我們?cè)诮M件 A 和組件 B ?import 引入 comA.css 和 comB.css。
comA.css
.title {
color: red;
}
comB.css
.title {
font-size: 14px;
}
最后打包出來(lái)的結(jié)果為:
.title {
color: red;
}
.title {
font-size: 14px;
}
我們希望,comA.css 兩者互不影響,可以發(fā)現(xiàn),雖然 A、B 兩個(gè)組件分別只引用了自己的 CSS 文件,但是 CSS 并沒(méi)有隔離,兩個(gè) CSS 文件是相互影響的!
隨著 SPA 的流行,JS 可以組件化,按需加載(路由按需加載、組件的 CSS 和 JS 都按需加載),這種情況下 CSS 作用域污染的問(wèn)題被放大,CSS 被按需加載后由于 CSS 全局污染的問(wèn)題,在加載出其他一部分代碼后,可能導(dǎo)致現(xiàn)有的頁(yè)面上會(huì)出現(xiàn)詭異的樣式變動(dòng)。這樣的問(wèn)題加大了發(fā)布的風(fēng)險(xiǎn)以及 debugger 的成本。
小編我從寫(xiě) Vue 到寫(xiě) React , Vue 的 scoped 完美的解決了 CSS 的作用域問(wèn)題,那么 React 如何解決 CSS 的作用域問(wèn)題呢?
解決 React 的 CSS 作用域污染方案:
- 方案一:namespaces
- 方案二:CSS in JS
- 方案三:CSS Modules
方案一:namespaces
“利用約定好的命名來(lái)隔離 CSS 的作用域
comA.css
.comA .title {
color: red;
}
.comA .……{
……
}
comB.css
.comB .title {
font-size: 14px;
}
.comB .……{
……
}
嗯,用 CSS 寫(xiě)命名空間寫(xiě)起來(lái)貌似有點(diǎn)累。
沒(méi)事我們有 CSS 預(yù)處理器,利用 less、sass、stylus 等預(yù)處理器,代碼依然簡(jiǎn)潔。
A.less
.comA {
.title {
color: red;
}
.…… {
……
}
}
B.less
.comB {
.title {
font-size: 14px;
}
.…… {
……
}
}
貌似很完美解決了 CSS 的作用域問(wèn)題,但是問(wèn)題來(lái)了,假設(shè) AB 組件是嵌套組件。
那么最后的渲染 DOM 結(jié)構(gòu)為:
<div class="comA">
<h1 class="title">組件A的titleh1>
<div class="comB">
<h1 class="title">組件組件的titleh1>
div>
div>
comA 的樣式又成功作用在了組件 B 上。
沒(méi)關(guān)系,還有解,所有的 class 名以命名空間為前綴。
<div class="comA">
<h1 class="comA__title">組件A的titleh1>
<div class="comB">
<h1 class="comB__title">組件組件的titleh1>
div>
div>
A.less
.comA {
&__title {
color: red;
}
}
B.less
.comB {
&__title {
font-size: 14px;
}
}
如果,我們的樣式還遵循 BEM (Block, Element, Modifier) 規(guī)范,那么,樣式名簡(jiǎn)直不要太長(zhǎng)!但是問(wèn)題確實(shí)也解決了,但約定畢竟是約定,靠約定和自覺(jué)來(lái)解決問(wèn)題畢竟不是好方法,在多人維護(hù)的業(yè)務(wù)代碼中這種約定來(lái)解決 CSS ?污染問(wèn)題也變得很難。
方案二:CSS in JS
“使用 JS 語(yǔ)言寫(xiě) CSS,也是 React 官方有推薦的一種方式。
從 React 文檔進(jìn)入
https://github.com/MicheleBertoli/css-in-js ,可以發(fā)現(xiàn)目前的 CSS in JS 的第三方庫(kù)有 60 余種。
看兩個(gè)比較大眾的庫(kù):
- reactCSS
- styled-components
reactCSS
“支持 React
、Redux、React Native、autoprefixed、Hover、偽元素和媒體查詢(http://reactcss.com/)
看下官網(wǎng)文檔 :
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 都轉(zhuǎn)化成了 JS 的寫(xiě)法,雖然沒(méi)有學(xué)習(xí)成本,但是這種轉(zhuǎn)變還是有一絲不適。
styled-components
“styled-components,目前社區(qū)里最受歡迎的一款 CSS in JS 方案(https://www.styled-components.com/)
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 使用了模板字符串,寫(xiě)法更接近 CSS 的寫(xiě)法。
方案三:CSS Modules
“利用 webpack 等構(gòu)建工具使 class 作用域?yàn)榫植俊?/p>
CSS 依然是還是 CSS
例如 webpack,配置 css-loader 的 options modules: true。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
};
modules 更具體的配置項(xiàng)參考:https://www.npmjs.com/package/css-loader
loader 會(huì)用唯一的標(biāo)識(shí)符 (identifier) 來(lái)替換局部選擇器。所選擇的唯一標(biāo)識(shí)符以模塊形式暴露出去。
示例:
webpack css-loader options
options: {
...,
modules: {
mode: 'local',
// 樣式名規(guī)則配置
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
...
App.js
...
import styles from "./App.css";
...
<header className={styles["header__wrapper"]}>
<h1 className={styles["title"]}>標(biāo)題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;
}
總結(jié)
(1)如果是 ui 組件庫(kù)中使用
“建議使用 namespaces 方案
原因:
- ui 組件庫(kù)維護(hù)人員基本固定,遵守約定的規(guī)范較為容易,可通過(guò)約定規(guī)范來(lái)解決不同組件 CSS 相互影響問(wèn)題
- 由于 ui 組件庫(kù)會(huì)應(yīng)用于整個(gè)公司的產(chǎn)品,在真正的業(yè)務(wù)場(chǎng)景中,雖然不建議,但是可能無(wú)法避免需要覆蓋組件樣式的特殊場(chǎng)景,如使用其他兩種方式,不能支持組件樣式覆蓋
(2)如果是業(yè)務(wù)代碼/業(yè)務(wù)組件中使用
“CSS in JS ?/ CSS Modules
業(yè)務(wù)代碼維護(hù)人員較多且不固定、代碼水平不一致,只通過(guò)規(guī)范來(lái)約束不靠譜,無(wú)法保證開(kāi)發(fā)人員嚴(yán)格遵守規(guī)范,不能根治 CSS 交叉影響問(wèn)題,但是從 debug 角度考慮,建議組件外層都添加一個(gè) namespaces 方面定位組件。然后加之 CSS in JS 或 CSS Modules 方案來(lái)解決 CSS 交叉影響問(wèn)題。
CSS in JS 和 CSS Modules 誰(shuí)優(yōu)誰(shuí)勝?
CSS Modules 會(huì)比 CSS in JS 的侵入性更小,CSS in JS 可以和 JS 共享變量,但個(gè)人更喜歡 CSS Modules ,但是誰(shuí)優(yōu)誰(shuí)勝無(wú)法武斷。
- 如果你的團(tuán)隊(duì)還沒(méi)有使用這任一技術(shù),需要考慮的是團(tuán)隊(duì)成員的感受
- 如果已經(jīng)在使用其中某一種方案,保持一致性即可,相信并這樣走下去
推薦閱讀
我的公眾號(hào)能帶來(lái)什么價(jià)值?(文末有送書(shū)規(guī)則,一定要看)
每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長(zhǎng)文建議收藏)
為什么現(xiàn)在面試總是面試造火箭?「一個(gè)有溫度的前端號(hào)」
長(zhǎng)按識(shí)別二維碼關(guān)注

點(diǎn)贊分享是對(duì)作者最大的支持!
