原文地址:react-css-modules
閱讀本文前建議了解 CSS Modules 的知識(shí)艺谆。墻裂推薦閱讀 Cam 的文章 CSS Modules詳解及React中實(shí)踐
React CSS Modules 實(shí)現(xiàn)了自動(dòng)化映射 CSS modules榨惰。每個(gè) CSS 類都被賦予了一個(gè)帶有全局唯一名字的本地標(biāo)識(shí)符。 CSS Modules 實(shí)現(xiàn)了模塊化和復(fù)用性静汤。
CSS Modules
CSS Mosules 碉堡了琅催。如果你對(duì) CSS Modules 還不夠熟悉,那么沒關(guān)系虫给,它只是一個(gè)使用 webpack 之類的模塊打包機(jī)加載 CSS 作用于特定文檔的概念藤抡。CSS module loader 將為每一個(gè) CSS 類在加載 CSS 文檔(確切的說,這個(gè)文檔就是Interoperable CSS)的時(shí)候生成一個(gè)唯一的名字抹估。你可以來看下這個(gè) CSS Modules 實(shí)踐例子——webpack-demo缠黍。
在React語法環(huán)境中,CSS Modules 看起來是這樣子的:
import React from 'react';
import styles from './table.css';
export default class Table extends React.Component {
render () {
return <div className={styles.table}>
<div className={styles.row}>
<div className={styles.cell}>A0</div>
<div className={styles.cell}>B0</div>
</div>
</div>;
組件渲染出來后會(huì)生成類似于這樣的一個(gè)標(biāo)記:
<div class="table__table___32osj">
<div class="table__row___2w27N">
<div class="table__cell___2w27N">A0</div>
<div class="table__cell___1oVw5">B0</div>
</div>
</div>
同時(shí)也會(huì)生成對(duì)應(yīng)的匹配那些CSS類的CSS文件药蜻,是不是碉堡了瓷式?!
webpack css-loader
CSS Modules 是一個(gè)可以被多種方法實(shí)現(xiàn)的規(guī)范语泽。react-css-modules
利用 webpack css-loader 所提供的功能啟用了現(xiàn)有的 CSS Modules 的集成贸典。
現(xiàn)有的問題
webpack 的 css-loader 本身有幾處劣勢:
- 你必須使用駝峰式類名。
- 無論何時(shí)構(gòu)建一個(gè)
className
你都必須使用style
對(duì)象踱卵。 - 混合類模塊以及全局 CSS 類不夠靈活瓤漏。
- 引用一個(gè)未定義的 CSS 模塊時(shí)解析結(jié)果為
undefines
,但并無相關(guān)警告提示。
React CSS Modules 組件自動(dòng)加載應(yīng)用了 styleName
特性的 CSS Modules ,例如:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';
class Table extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
}
export default CSSModules(Table, styles);
使用 react-css-modules
好處多多:
你不用再被強(qiáng)制使用駝峰式命名規(guī)則
你不必每次使用一個(gè) CSS 模塊時(shí)還要引用
styles
對(duì)象蔬充。-
有一個(gè)明顯的區(qū)別在全局 CSS 和 CSS Modules 之間,示例如下:
<div className='global-css' styleName='local-module'></div>
當(dāng)
styleName
引用一個(gè)為定義的 CSS Module 時(shí)班利,你會(huì)得到一個(gè)警告信息饥漫。(errorWhenNotFound
選項(xiàng))你可以為每一個(gè)
ReactElement
只使用單獨(dú)的 CSS Module。(allowMultiple
選項(xiàng))罗标。
實(shí)現(xiàn)
react-css-modules
擴(kuò)展了目標(biāo)組件的 render
方法庸队。它將根據(jù) styleName
的值在關(guān)聯(lián)的 style
對(duì)象中查找對(duì)應(yīng)的 CSS Modules,并為 ReactElement className
屬性值添加相匹配的獨(dú)一無二的 CSS 類名闯割。
碉堡了彻消!
你可以參照下這個(gè)例子進(jìn)一步加深印象,react-css-modules-examples宙拉。
用法
設(shè)置包括:
- 設(shè)置一個(gè)
module bundler
加載 Interoperable CSS宾尚。 - 使用
react-css-modules
修整你的組件。
如何設(shè)置一個(gè) module bundler
呢谢澈?
webpack
開發(fā)模式
開發(fā)環(huán)境下煌贴,若你想啟用 Sourcemaps,并要使用 webpack 的 Hot Module Replacement (HMR锥忿,熱替換)牛郑。style-loader
已經(jīng)支持 HMR。因此敬鬓,Hot Module Replacement 開箱即用淹朋。
設(shè)置步驟:
- 安裝
style-loader
。 - 安裝
css-loader
钉答。 - 設(shè)置
/\.css$/
加載器础芍,如下:
{
test: /\.css$/,
loaders: [
'style?sourceMap',
'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]'
]
}
生產(chǎn)模式
在生產(chǎn)環(huán)境中,如果你想把CSS單獨(dú)提取出來的話希痴,你需要了解這樣做的好處和壞處者甲。
優(yōu)點(diǎn):
- 更少的樣式標(biāo)簽
- CSS SourceMap
- CSS 并行請求
- CSS 緩存分離
- 頁面渲染更快(更少的代碼以及更少的 DOM 操作)
缺點(diǎn):
- 額外的 HTTP 請求
- 較長的編譯時(shí)間
- 較復(fù)雜的配置
- 不支持變更運(yùn)行環(huán)境公共路徑
- 不支持 Hot Module Replacement
設(shè)置步驟:
安裝
style-loader
安裝
css-loader
使用
extract-text-webpack-plugin
提取 CSS 到一個(gè)單獨(dú)的樣式表。-
設(shè)置
/\.css$/
加載器:{ test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]') }
-
設(shè)置
extract-text-webpack-plugin
插件:new ExtractTextPlugin('app.css', { allChunks: true })
完整實(shí)例請參照 webpack-demo 或者 react-css-modules-examples砌创。
Browserify(如果你是使用這個(gè)構(gòu)建工具的話)
請參考 css-modulesify虏缸。
擴(kuò)展組件樣式
使用 styles
屬性重寫默認(rèn)的組件樣式。
示例如下:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';
class Table extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
}
export default CSSModules(Table, styles);
在這個(gè)例子中嫩实,CSSModules
被用來美化 Table
組件通過引用 ./table.css
CSS 模塊刽辙。當(dāng) Table
組件渲染完畢,它將使用 styles
對(duì)象的屬性構(gòu)建 className
的值甲献。
使用 styles
屬性你可以覆蓋組件默認(rèn)的 styles
對(duì)象宰缤。例如:
import customStyles from './table-custom-styles.css';
<Table styles={customStyles} />;
Interoperable CSS 可以擴(kuò)展其他 ICSS。利用這個(gè)功能可以擴(kuò)展默認(rèn)樣式,例如:
/* table-custom-styles.css */
.table {
composes: table from './table.css';
}
.row {
composes: row from './table.css';
}
/* .cell {
composes: cell from './table.css';
} */
.table {
width: 400px;
}
.cell {
float: left; width: 154px; background: #eee; padding: 10px; margin: 10px 0 10px 10px;
}
在這個(gè)例子中慨灭,table-custom-styles.css
有選擇的擴(kuò)展了 table.css
(Table
組件的默認(rèn)樣式)朦乏。
這里是一個(gè)更直觀的實(shí)踐例子:UsingStylesProperty example`。
style
屬性
包裝過的組件繼承了 styles
屬性氧骤,該屬性描述了 CSS 模塊和 CSS 類之間的映射關(guān)系呻疹。
class extends React.Component {
render () {
<div>
<p styleName='foo'></p>
<p className={this.props.styles.foo}></p>
</div>;
}
}
在上面示例中,styleName='foo'
和 className={this.props.styles.foo}
是等價(jià)的筹陵。
styles
屬性是為 Loops and Child Components
實(shí)現(xiàn)組件包裝而特意設(shè)計(jì)的刽锤!
Loops and Child Components
styleName
不能去定義一個(gè)由其他組件生成的 React元素的樣式 。例如:
import React from 'react';
import CSSModules from 'react-css-modules';
import List from './List';
import styles from './table.css';
class CustomList extends React.Component {
render () {
let itemTemplate;
itemTemplate = (name) => {
return <li styleName='item-template'>{name}</li>;
};
return <List itemTemplate={itemTemplate} />;
}
}
export default CSSModules(CustomList, styles);
上面的實(shí)例將不會(huì)工作朦佩。CSSModules 被用來包裝 CustomList
組件并思。然而,它是呈現(xiàn) itemTemplage
的列表組件语稠。
為了解決這個(gè)問題宋彼,包裝過的組件繼承了樣式屬性,這樣你可以將它作為一個(gè)常規(guī)的 CSS 模塊對(duì)象來使用颅筋。
因此宙暇,前面的例子可以改寫為這樣:
import React from 'react';
import CSSModules from 'react-css-modules';
import List from './List';
import styles from './table.css';
class CustomList extends React.Component {
render () {
let itemTemplate;
itemTemplate = (name) => {
return <li className={this.props.styles['item-template']}>{name}</li>;
};
return <List itemTemplate={itemTemplate} />;
}
}
export default CSSModules(CustomList, styles);
在把子組件傳遞給渲染組件之前,如果你使用了 CSSMmodules
包裝這個(gè)子組件议泵,那么你就可以在這個(gè)子組件內(nèi)使用 styleName
屬性了占贫。例如:
import React from 'react';
import CSSModules from 'react-css-modules';
import List from './List';
import styles from './table.css';
class CustomList extends React.Component {
render () {
let itemTemplate;
itemTemplate = (name) => {
return <li styleName='item-template'>{name}</li>;
};
itemTemplate = CSSModules(itemTemplate, this.props.styles);
return <List itemTemplate={itemTemplate} />;
}
}
export default CSSModules(CustomList, styles);
包裝
/**
* @typedef CSSModules~Options
* @see {@link https://github.com/gajus/react-css-modules#options}
* @property {Boolean} allowMultiple
* @property {Boolean} errorWhenNotFound
*/
/**
* @param {Function} Component
* @param {Object} defaultStyles CSS Modules class map.
* @param {CSSModules~Options} options
* @return {Function}
*/
你需要用 react-css-modules
包裝你的組件,例如:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';
class Table extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
}
export default CSSModules(Table, styles);
就這么簡單先口!
顧名思義型奥,react-css-modules
和 ES7 decorators 語法是兼容的。例如:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';
@CSSModules(styles)
export default class extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
}
簡直碉堡了碉京!
這里有一個(gè)用 webpack 構(gòu)建的示例厢汹,你可以看下 react-css-modules-examples
Options
CSSModules
函數(shù)提供了一些選項(xiàng)在第三個(gè)參數(shù)的位置上。
CSSModules(Component, styles, options);
或者作為第二個(gè)參數(shù):
@CSSModules(styles, options);
allowMultiple
默認(rèn):false
谐宙。
允許多個(gè)樣式模塊名字烫葬。
當(dāng) false
,以下會(huì)引起一個(gè)錯(cuò)誤凡蜻。
<div styleName='foo bar' />
errorWhenNotFound
默認(rèn):true
搭综。
當(dāng) styleName
不能匹配到一個(gè)未定義的 CSS Module 時(shí)將拋出一個(gè)錯(cuò)誤。
SASS, SCSS, LESS 以及其他 CSS 預(yù)處理器
Interoperable CSS 和 CSS 預(yù)處理器是兼容的划栓。使用預(yù)處理器兑巾,你只需添加這個(gè)預(yù)處理器到 loaders
的數(shù)組中即可。例如在這個(gè) webpack 的例子中忠荞,它就像安裝 sass-loader
一樣簡單蒋歌,添加 !sass
到 style-loader
加載器查詢的末尾(加載器是從右到左被依次執(zhí)行的):
{
test: /\.scss$/,
loaders: [
'style',
'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
'resolve-url',
'sass'
]
}
開啟 Sourcemaps
開啟 CSS Source maps帅掘,需要在 css-loader
和 sass-loader
中添加參數(shù) sourceMap
{
test: /\.scss$/,
loaders: [
'style?sourceMap',
'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
'resolve-url',
'sass?sourceMap'
]
}
類組合
CSS Mosules 促進(jìn)了類組合模式,也就是說堂油,每一個(gè)用在組件里的 CSS Module 應(yīng)該定義描述一個(gè)元素所需的全部屬性修档。如:
.box {
width: 100px;
height: 100px;
}
.empty {
composes: box;
background: #4CAF50;
}
.full {
composes: box;
background: #F44336;
}
類組合促進(jìn)了標(biāo)記和語義化樣式更好的分離,如果沒有 CSS Modules称诗,這點(diǎn)將難以實(shí)現(xiàn)萍悴。
因?yàn)?CSS Module 中的類名是本地的,允許你使用諸如 'empty' 或 'full' 這些通用的類名寓免,而無需再加上'box-' 前綴,簡直完美<莆袜香!
想學(xué)更多的類組合規(guī)則?我建議你讀下 Glen Maddern 的關(guān)于 CSS Modules 的文章鲫惶,還有官方文檔 spec of the CSS Modules蜈首。
類組合解決了什么問題?
設(shè)想下有這么個(gè)例子:
.box {
width: 100px;
height: 100px;
}
.box-empty {
background: #4CAF50;
}
.box-full {
background: #F44336;
}
<div class='box box-empty'></div>
這是標(biāo)準(zhǔn)的 OOCSS 模式欠母,這種模式最大的問題就是欢策,如果你想改變樣式,你幾乎每次還要修改 HTML赏淌。
類組合也可以使用 CSS 預(yù)處理器
接下來是一個(gè)學(xué)習(xí)實(shí)踐踩寇,以加深對(duì)類組合本質(zhì)的理解。CSS Modules 支持一個(gè)本機(jī)方法六水,該方法是要組合的 CSS Mosules 使用 composes
關(guān)鍵字實(shí)現(xiàn)的俺孙。CSS 預(yù)處理不是必須的。
在 SCSS 中你可以使用 @extend
關(guān)鍵字掷贾,和使用 Mixin Directives去寫組合睛榄,例如:
使用 @extend
:
%box {
width: 100px;
height: 100px;
}
.box-empty {
@extend %box;
background: #4CAF50;
}
.box-full {
@extend %box;
background: #F44336;
}
編譯后,得到:
.box-empty,
.box-full {
width: 100px;
height: 100px;
}
.box-empty {
background: #4CAF50;
}
.box-full {
background: #F44336;
}
使用 mixins:
@mixin box {
width: 100px;
height: 100px;
}
.box-empty {
@include box;
background: #4CAF50;
}
.box-full {
@include box;
background: #F44336;
}
編譯后想帅,得到:
.box-empty {
width: 100px;
height: 100px;
background: #4CAF50;
}
.box-full {
width: 100px;
height: 100px;
background: #F44336;
}
全局樣式
CSS Modules 不會(huì)限制你使用全局 CSS场靴。用法如下:
:global .foo {
}
但是呢,還是請你謹(jǐn)慎使用全局樣式港准。在使用 CSS Modules 過程中旨剥,只有少數(shù)情況下才會(huì)用到全局樣式,例如:normalization叉趣。
多個(gè) CSS Modules
避免使用多個(gè) CSS Modules 去描述單個(gè)元素泞边。詳見本文檔前面的 類組合 部分介紹。
但是如果你非要使用多個(gè) CSS Modules 去描述一個(gè)元素疗杉,那么就開啟 allowMultiple
選項(xiàng)阵谚。(參見文檔前面 選項(xiàng) 部分)蚕礼。當(dāng)多個(gè) CSS Modules 被用來描述一個(gè)元素時(shí),react-css-modules
將會(huì)為每一個(gè)在 styleName
聲明中匹配的 CSS Module 附加一個(gè)獨(dú)一無二的類名梢什。如:
.button {
}
.active {
}
<div styleName='button active'></div>
這會(huì)把Interoperable CSS 和 CSS class 都映射到目標(biāo)元素上奠蹬。