[譯] react-css-modules

原文地址:react-css-modules
閱讀本文前建議瞭解 CSS Modules 的知識。牆裂推薦閱讀 Cam 的文章 CSS Modules詳解及React中實踐javascript

React CSS Modules 實現了自動化映射 CSS modules。每一個 CSS 類都被賦予了一個帶有全局惟一名字的本地標識符。 CSS Modules 實現了模塊化和複用性。(https://github.com/minooo/React-Study/tree/master/step-02css

CSS Modules

CSS Mosules 碉堡了。若是你對 CSS Modules 還不夠熟悉,那麼不要緊,它只是一個使用 webpack 之類的模塊打包機加載 CSS 做用於特定文檔的概念。CSS module loader 將爲每個 CSS 類在加載 CSS 文檔(確切的說,這個文檔就是Interoperable CSS)的時候生成一個惟一的名字。你能夠來看下這個 CSS Modules 實踐例子——webpack-demohtml

在React語法環境中,CSS Modules 看起來是這樣子的:java

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>;

組件渲染出來後會生成相似於這樣的一個標記:react

<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

webpack css-loader

CSS Modules 是一個能夠被多種方法實現的規範。react-css-modules 利用 webpack css-loader 所提供的功能啓用了現有的 CSS Modules 的集成。git

現有的問題

webpack 的 css-loader 自己有幾處劣勢:github

  • 你必須使用駝峯式類名。web

  • 不管什麼時候構建一個 className 你都必須使用 style 對象。segmentfault

  • 混合類模塊以及全局 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 都映射到目標元素上。

相關文章
相關標籤/搜索