jd-smart-fe / shared

共享文档
MIT License
25 stars 4 forks source link

CSS Modules详解及在React中的实践 #11

Open zxiaohong opened 6 years ago

zxiaohong commented 6 years ago

CSS Modules 详解及应用

在最近的React项目中,遇到了CSS处理的问题:

  1. 由于是多人开发,各自对样式类的命名规则不统一
  2. 样式全局有效,产生了样式覆盖,不得不重新定义样式类或者使用 !important;

平时的项目开发中,还会有一些类似的CSS问题:

使用CSS模块化可以很好的解决上述问题。目前主要分为两类:

一类是彻底抛弃 CSS,使用 JS 或 JSON 来写样式。Radium, jsxstyle ,react-style 属于这一类。优点是能给 CSS 提供 JS 同样强大的模块化能力;缺点是不能利用成熟的 CSS 预处理器(或后处理器) Sass/Less/PostCSS, :hover 和 :active 伪类处理起来复杂。

另一类是依旧使用 CSS,但使用 JS 来管理样式依赖,代表是CSS Modules。CSS Modules 能最大化地结合现有 CSS 生态和 JS 模块化能力。发布时依旧编译出单独的 JS 和 CSS。它并不依赖于 React,只要你使用 Webpack,可以在 Vue/Angular/jQuery 中使用。

相关概念

overview

CSS Modules是什么

原理

使用CSS Modules 有什么优势

启用方法

CSS Modules 通过webpack配置引入项目,不依赖于任何框架,只要使用webpack配置后就可以用于React/Vue/Angular/jQuery 项目中.

  1. 在webpack.config.js的module中添加如下配置:
    module.exports = {
    entry: __dirname + '/index.js',
    output: {
    publicPath: '/',
    filename: './bundle.js'
    },
    module: {
    rules: [
      ...
      {
      test: /\.css$/,
      use:[
          {loader:'style-loader'},
          {
            loader:'css-loader',
            option:{
                modules:true
            }
          }
        ]
      },
    ]
    }
    }

    上面代码中,关键的是在css-loader的option里配置option:{modules:true},表示打开 CSS Modules 功能。

使用这种配置方式,css-loader默认将类名编译为唯一的hash串,但不利于class类名的语义化,如:

/*header.css*/
.root {
    text-align: center;
}
.header {
    background-color: #536587;
    height: 150px;
    padding: 20px;
    color: white;
}
.title {
    font-size: 1.5em;
}
/*header.js*/

import React from 'react';
import styles from './header.css';

const Header =()=>{
  return (
    <div className={styles.root}>
        <header className={styles.header}>
          <h1 className={styles.title}>Welcome to React</h1>
        </header>
    </div>
  )
}
export default Header;

<h1/>将被编译为:<h1 class="_1yHZnBWcll0vdb7BCk5Ufm">Welcome to React</h1>

同时 style.css文件将被编译为

    ._1yHZnBWcll0vdb7BCk5Ufm {
        font-size: 1.5em;
    }

因此,需要在配置webpack时多做一点,定制编译生成的哈希类名:

rules:[
  {
    test:/.css$/,
    use:[
      {loader:'style-loader'},
      {loader:'css-loader',
        option:{modules:true,localIdentName:'[path][name]__[local]-[hase:base64:5]'}
        ]
  }
]

配置localIdentName之后 上面的类名将生成 如下格式:

localidentname

CSS Modules的使用

/ 以上与下面等价 / :local(.normal) { color: green; } / 定义全局样式 / :global(.btn) { color: red; } / 定义多个全局样式 / :global { .link { color: green; } .box { color: yellow; } }


- ####  Compose 组合Class

  对于样式复用,CSS Modules 只提供了唯一的方式来处理:`composes` 组合

##### 复用内部样式  

 ```css    
.userInfoBox {
  position: relative;
  padding: 0 24px 0 32px;
  height: 40px;
  color: white;
  line-height: 40px;
  background: #1f1f1f;
  transition: background .2s ease 0s;
}

.docDownloadBox {
  composes: userInfoBox;
  margin-right: 10px;
  border-right: 1px solid #444;
}

使用docDownloadBox类的HTML

<a className={styles.docDownloadBox}>
  文档&下载
</a>

编译为两个class composes

复用外部样式
/* settings.css */
.primary-color {
  color: #f40;
}

/* components/Button.css */
.base { /* 所有通用的样式 */ }

.primary {
  composes: base;
  composes: primary-color from './settings.css';
  /* primary 其它样式 */
}

对于大多数项目,有了 composes 后已经不再需要 Sass/Less/PostCSS。但如果你想用的话,由于 composes 不是标准的 CSS 语法,编译时会报错。就只能使用预处理器自己的语法来做样式复用了。

  • 层叠多个class

配合classnames使用

npm install --save classnames

直接使用
/* components/submit-button.js */
import React, { Component } from 'react';
import classNames from 'classnames';
import styles from './submit-button.css';

export default class SubmitButton extends Component {
  render () {
    let text = this.props.store.submissionInProgress ? 'Processing...' : 'Submit';
    let className = classNames({
      [`${styles.base}`]: true,
      [`${styles.inProgress}`]: this.props.store.submissionInProgress,
      [`${styles.error}`]: this.props.store.errorOccurred,
      [`${styles.disabled}`]: this.props.form.valid,
    });
    return <button className={className}>{text}</button>;
  }
};
使用 classnames/bind
/* components/submit-button.js */
import React, { Component } from 'react';
import classNames from 'classnames/bind';
import styles from './submit-button.css';

let cx = classNames.bind(styles);

export default class SubmitButton extends Component {
  render () {
    let text = this.props.store.submissionInProgress ? 'Processing...' : 'Submit';
    let className = cx({
      base: true,
      inProgress: this.props.store.submissionInProgress,
      error: this.props.store.errorOccurred,
      disabled: this.props.form.valid,
    });
    return <button className={className}>{text}</button>;
  }
};

CSS Moduels 本身没有变量的概念,如果需要使用变量,要借助预处理器/后处理器。

定义变量

安装 PostCSS 和 postcss-icss-values

  npm install --save postcss-loader postcss-icss-values

把postcss-loader加入webpack.config.js。

{
  test: /\.css$/,
  use: [
    require.resolve('style-loader'),
    {
      loader: require.resolve('css-loader'),
      options: {
        importLoaders: 1,
        modules:true,
        localIdentName:'[name]--[local]--[hash:base64:5]'
      } 
    },
    {
      loader: require.resolve('postcss-loader'),
      options: {
        ident: 'postcss',
        plugins: () => [
          require('postcss-flexbugs-fixes'),
          autoprefixer({
            browsers: [
              '>1%',
              'last 4 versions',
              'Firefox ESR',
              'not ie < 9', // React doesn't support IE8 anyway
            ],
            flexbox: 'no-2009',
          }),
          require('postcss-icss-values')
        ],
      },
    },
  ],
},

然后,定义变量,如variable.css

@value primary  #0c77f8;
@value green    #aaf200;

变量和值之间 写不写冒号都可以,但如果和sass配合使用,不要写分号 一次只定义一个变量

在其他css文件中使用变量,

App.css中引入变量

@value variables: "./variable.css";
@value primary from variables;

//或者直接 @value primary from "variables.css"

.title {
  background-color: primary;
}
css和js共享变量
//variable.css
@value primary #0c77f8;
//App.js
import { primary } from './variable.css';
console.log(primary); // -> #0c77f8

css变量在js中只读

export default class Table extends React.Component { render () { return

A0
B0
</div>;

} export default Table;


使用`babel-plugin-react-css-modules`:
1. 不必使用驼峰命名;
2. 不用使用styles Object
3. 可以和全局变量自由组合;

`<div className='global-css' styleName='local-module'></div>`

参考文献:
1. [CSS Modules 详解及 React 中实践](https://github.com/camsong/blog/issues/5)
2. [CSS Modules 用法教程 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2016/06/css_modules.html)
3. [CSS Modules 入门及 React 中实践 | AlloyTeam](http://www.alloyteam.com/2017/03/getting-started-with-css-modules-and-react-in-practice/)
4. [GitHub - css-modules/css-modules: Documentation about css-modules](https://github.com/css-modules/css-modules)
5. [GitHub - css-modules/postcss-icss-values: Pass arbitrary constants between your module files](https://github.com/css-modules/postcss-icss-values)
6. [GitHub - gajus/babel-plugin-react-css-modules: Transforms styleName to className using compile time CSS module resolution.](https://github.com/gajus/babel-plugin-react-css-modules#how-does-it-work)