問題: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 做用域污染方案:
利用約定好的命名來隔離 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 污染問題也變得很難。
使用 JS 語言寫 CSS,也是 React 官方有推薦的一種方式。
從React文檔進入
github.com/MicheleBert… ,能夠發現目前的 CSS in JS 的第三方庫有60餘種。
看兩個比較大衆的庫:
支持 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,目前社區裏最受歡迎的一款 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 的寫法。
利用 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 方案
緣由:
(2)若是是業務代碼/業務組件中使用
CSS in JS / CSS Modules
業務代碼維護人員較多且不固定、代碼水平不一致,只經過規範來約束不靠譜,沒法保證開發人員嚴格遵照規範,不能根治 CSS 交叉影響問題,可是從 debug 角度考慮,建議組件外層都添加一個 namespaces 方面定位組件。而後加之 CSS in JS 或 CSS Modules 方案來解決 CSS 交叉影響問題。
CSS Modules 會比 CSS in JS 的侵入性更小,CSS in JS 能夠和 JS 共享變量,但我的更喜歡 CSS Modules ,可是誰優誰勝沒法武斷。