React CSS Modules(譯文)

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

  • 你必須使用駝峰式類名。
  • 無論何時(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 并行請(qǐng)求
  • CSS 緩存分離
  • 頁面渲染更快(更少的代碼以及更少的 DOM 操作)

缺點(diǎn):

  • 額外的 HTTP 請(qǐng)求
  • 較長(zhǎng)的編譯時(shí)間
  • 較復(fù)雜的配置
  • 不支持變更運(yùn)行環(huán)境公共路徑
  • 不支持 Hot Module Replacement

extract-text-webpack-plugin

設(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í)例請(qǐng)參照 webpack-demo 或者 react-css-modules-examples。

Browserify(如果你是使用這個(gè)構(gòu)建工具的話)

請(qǐng)參考 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);

就這么簡(jiǎn)單!

顧名思義,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>;
    }
}

簡(jiǎn)直碉堡了!

這里有一個(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 一樣簡(jiǎn)單,添加 !sassstyle-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-loadersass-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-' 前綴,簡(jiǎn)直完美??!

想學(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 {

}

但是呢,還是請(qǐng)你謹(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)元素上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容