[译] 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 都映射到目标元素上。

相关文章
相关标签/搜索