原文地址:react-css-modules
閱讀本文前建議瞭解 CSS Modules 的知識。牆裂推薦閱讀 Cam 的文章 CSS Modules詳解及React中實踐
React CSS Modules 實現了自動化對映 CSS modules。每個 CSS 類都被賦予了一個帶有全域性唯一名字的本地識別符號。 CSS Modules 實現了模組化和複用性。(https://github.com/minooo/React-Study/tree/master/step-02)
CSS Modules
CSS Mosules 碉堡了。如果你對 CSS Modules 還不夠熟悉,那麼沒關係,它只是一個使用 webpack 之類的模組打包機載入 CSS 作用於特定文件的概念。CSS module loader 將為每一個 CSS 類在載入 CSS 文件(確切的說,這個文件就是Interoperable CSS)的時候生成一個唯一的名字。你可以來看下這個 CSS Modules 實踐例子——webpack-demo。
在React語法環境中,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>;
元件渲染出來後會生成類似於這樣的一個標記:
<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>
同時也會生成對應的匹配那些CSS類的CSS檔案,是不是碉堡了?!
webpack css-loader
CSS Modules 是一個可以被多種方法實現的規範。react-css-modules
利用 webpack css-loader 所提供的功能啟用了現有的 CSS Modules 的整合。
現有的問題
webpack 的 css-loader 本身有幾處劣勢:
-
你必須使用駝峰式類名。
-
無論何時構建一個
className
你都必須使用style
物件。 -
混合類模組以及全域性 CSS 類不夠靈活。
-
引用一個未定義的 CSS 模組時解析結果為
undefined
,但並無相關警告提示。
React CSS Modules 元件自動載入應用了 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
好處多多:
-
你不用再被強制使用駝峰式命名規則
-
你不必每次使用一個 CSS 模組時還要引用
styles
物件。 -
有一個明顯的區別在全域性 CSS 和 CSS Modules 之間,示例如下:
<div className=`global-css` styleName=`local-module`></div>
-
當
styleName
引用一個為定義的 CSS Module 時,你會得到一個警告資訊。(errorWhenNotFound
選項) -
你可以為每一個
ReactElement
只使用單獨的 CSS Module。(allowMultiple
選項)。
實現
react-css-modules
擴充套件了目標元件的 render
方法。它將根據 styleName
的值在關聯的 style
物件中查詢對應的 CSS Modules,併為 ReactElement className
屬性值新增相匹配的獨一無二的 CSS 類名。
碉堡了!
你可以參照下這個例子進一步加深印象,react-css-modules-examples。
用法
設定包括:
-
設定一個
module bundler
載入 Interoperable CSS。 -
使用
react-css-modules
修整你的元件。
如何設定一個 module bundler
呢?
webpack
開發模式
開發環境下,若你想啟用 Sourcemaps,並要使用 webpack 的 Hot Module Replacement (HMR,熱替換)。style-loader
已經支援 HMR。因此,Hot Module Replacement 開箱即用。
設定步驟:
-
安裝
style-loader
。 -
安裝
css-loader
。 -
設定
/.css$/
載入器,如下:
{
test: /.css$/,
loaders: [
`style?sourceMap`,
`css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]`
]
}
生產模式
在生產環境中,如果你想把CSS單獨提取出來的話,你需要了解這樣做的好處和壞處。
優點:
更少的樣式標籤
CSS SourceMap
CSS 並行請求
CSS 快取分離
頁面渲染更快(更少的程式碼以及更少的 DOM 操作)
缺點:
額外的 HTTP 請求
較長的編譯時間
較複雜的配置
不支援變更執行環境公共路徑
不支援 Hot Module Replacement
設定步驟:
-
安裝
style-loader
-
安裝
css-loader
-
使用
extract-text-webpack-plugin
提取 CSS 到一個單獨的樣式表。 -
設定
/.css$/
載入器:{ test: /.css$/, loader: ExtractTextPlugin.extract(`style`, `css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]`) }
-
設定
extract-text-webpack-plugin
外掛:new ExtractTextPlugin(`app.css`, { allChunks: true })
完整例項請參照 webpack-demo 或者 react-css-modules-examples。
Browserify(如果你是使用這個構建工具的話)
請參考 css-modulesify。
擴充套件元件樣式
使用 styles
屬性重寫預設的元件樣式。
示例如下:
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);
在這個例子中,CSSModules
被用來美化 Table
元件通過引用 ./table.css
CSS 模組。當 Table
元件渲染完畢,它將使用 styles
物件的屬性構建 className
的值。
使用 styles
屬性你可以覆蓋元件預設的 styles
物件。例如:
import customStyles from `./table-custom-styles.css`;
<Table styles={customStyles} />;
Interoperable CSS 可以擴充套件其他 ICSS。利用這個功能可以擴充套件預設樣式,例如:
/* 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;
}
在這個例子中,table-custom-styles.css
有選擇的擴充套件了 table.css
(Table
元件的預設樣式)。
這裡是一個更直觀的實踐例子:UsingStylesProperty example`。
style
屬性
包裝過的元件繼承了 styles
屬性,該屬性描述了 CSS 模組和 CSS 類之間的對映關係。
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}
是等價的。styles
屬性是為 Loops and Child Components
實現元件包裝而特意設計的!
Loops and Child Components
styleName
不能去定義一個由其他元件生成的 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);
上面的例項將不會工作。CSSModules 被用來包裝 CustomList
元件。然而,它是呈現 itemTemplage
的列表元件。
為了解決這個問題,包裝過的元件繼承了樣式屬性,這樣你可以將它作為一個常規的 CSS 模組物件來使用。
因此,前面的例子可以改寫為這樣:
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
包裝這個子元件,那麼你就可以在這個子元件內使用 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>;
}
}
簡直碉堡了!
這裡有一個用 webpack 構建的示例,你可以看下 react-css-modules-examples
Options
CSSModules
函式提供了一些選項在第三個引數的位置上。
CSSModules(Component, styles, options);
或者作為第二個引數:
@CSSModules(styles, options);
allowMultiple
預設:false
。
允許多個樣式模組名字。
當 false
,以下會引起一個錯誤。
<div styleName=`foo bar` />
errorWhenNotFound
預設:true
。
當 styleName
不能匹配到一個未定義的 CSS Module 時將丟擲一個錯誤。
SASS, SCSS, LESS 以及其他 CSS 前處理器
Interoperable CSS 和 CSS 前處理器是相容的。使用前處理器,你只需新增這個前處理器到 loaders
的陣列中即可。例如在這個 webpack 的例子中,它就像安裝 sass-loader
一樣簡單,新增 !sass
到 style-loader
載入器查詢的末尾(載入器是從右到左被依次執行的):
{
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
中新增引數 sourceMap
{
test: /.scss$/,
loaders: [
`style?sourceMap`,
`css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]`,
`resolve-url`,
`sass?sourceMap`
]
}
類組合
CSS Mosules 促進了類組合模式,也就是說,每一個用在元件裡的 CSS Module 應該定義描述一個元素所需的全部屬性。如:
.box {
width: 100px;
height: 100px;
}
.empty {
composes: box;
background: #4CAF50;
}
.full {
composes: box;
background: #F44336;
}
類組合促進了標記和語義化樣式更好的分離,如果沒有 CSS Modules,這點將難以實現。
因為 CSS Module 中的類名是本地的,允許你使用諸如 `empty` 或 `full` 這些通用的類名,而無需再加上`box-` 字首,簡直完美!!
想學更多的類組合規則?我建議你讀下 Glen Maddern 的關於 CSS Modules 的文章,還有官方文件 spec of the CSS Modules。
類組合解決了什麼問題?
設想下有這麼個例子:
.box {
width: 100px;
height: 100px;
}
.box-empty {
background: #4CAF50;
}
.box-full {
background: #F44336;
}
<div class=`box box-empty`></div>
這是標準的 OOCSS 模式,這種模式最大的問題就是,如果你想改變樣式,你幾乎每次還要修改 HTML。
類組合也可以使用 CSS 前處理器
接下來是一個學習實踐,以加深對類組合本質的理解。CSS Modules 支援一個本機方法,該方法是要組合的 CSS Mosules 使用 composes
關鍵字實現的。CSS 預處理不是必須的。
在 SCSS 中你可以使用 @extend
關鍵字,和使用 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 不會限制你使用全域性 CSS。用法如下:
:global .foo {
}
但是呢,還是請你謹慎使用全域性樣式。在使用 CSS Modules 過程中,只有少數情況下才會用到全域性樣式,例如:normalization。
多個 CSS Modules
避免使用多個 CSS Modules 去描述單個元素。詳見本文件前面的 類組合 部分介紹。
但是如果你非要使用多個 CSS Modules 去描述一個元素,那麼就開啟 allowMultiple
選項。(參見文件前面 選項 部分)。當多個 CSS Modules 被用來描述一個元素時,react-css-modules
將會為每一個在 styleName
宣告中匹配的 CSS Module 附加一個獨一無二的類名。如:
.button {
}
.active {
}
<div styleName=`button active`></div>
這會把Interoperable CSS 和 CSS class 都對映到目標元素上。