[譯] react-css-modules

minooo發表於2016-03-03

原文地址: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

extract-text-webpack-plugin

設定步驟:

  • 安裝 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-modulesES7 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 一樣簡單,新增 !sassstyle-loader 載入器查詢的末尾(載入器是從右到左被依次執行的):

{
    test: /.scss$/,
    loaders: [
        `style`,
        `css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]`,
        `resolve-url`,
        `sass`
    ]
}

開啟 Sourcemaps

開啟 CSS Source maps,需要在 css-loadersass-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 都對映到目標元素上。

相關文章