CSS Modules在React中的应用

由于CSS是全局的,所以在写组件的时候,经常会遇到CSS命名重复导致样式覆盖(冲突),所以我们在写CSS的时候一般会这么处理

  • 写复杂的class名,降低冲突的概率
  • 给组件最外层元素添加一个class,限制范围

但是这样做也不一定可以保证不会冲突,而且还导致class名字太复杂,嵌套太深,可维护性差,那么是否可以将CSS也像JS那样,实现模块化呢?答案是肯定的。CSS模块化方案很多,但是主要的就三类:

比较常用的有BEM,SMACSS和OOCSS,但是存在以下问题:

  • JS CSS之间依旧没有打通变量和选择器
  • 命名太复杂

直接在JS中写CSS并内联样式,例如aphrodite,babel-plugin-css-in-js等(点击查看所有CSS in JS的解决方案)但是存在以下问题:

  • 样式代码可能会重复出现
  • 写法上已经和传统的CSS不再相似(例如aphrodite,写法类似于React Native中样式的写法)
  • 不能利用成熟的CSS预处理器(或后处理器)

使用JS编译原生CSS文件,使其具有模块化,典型的就是CSS Modules。只要使用到webpack,就会使用到css-loader,在webpack中稍加配置即可使用

配置css-loader启动css modules

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// webpack.config.js
  {
    test: /\.css$/,
    use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: {
        loader: 'css-loader',
        options:{
          modules: true,
          minimize: true,
          localIdentName: '[path][name]__[local]--[hash:base64:5]'
        }
      }
    })
  },
1
2
3
4
// Button.css
.button{
  font-size: 10px;
}
1
2
3
4
// Button.js
import styles from './Button.css'
console.log(styles)
buttonElement.outerHTML = `<div class=${styles.button}>Button</div>`

console出来的styles如下:

1
2
3
4
5
6
{
 button:"src-components-Button-index__button--1mmZb"
 large:"src-components-Button-index__large--2atzR"
 normall:"src-components-Button-index__normall--3prnh"
 small:"src-components-Button-index__small--34Wrr"
}

通过以上配置,css loader为我们生成如上class名字,其中1mmZb是按照[hash:base64:5]生成的,大大降低了命名冲突的概率。

通过这些简单的处理,CSS Modules 实现了以下几点:

  • 所有样式都是局部作用域的,解决了全局污染问题
  • class 名生成规则配置灵活,可以此来压缩 class 名
  • 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
  • 依然是熟悉的CSS,学习成本低

直接在className处使用css中的class名即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component } from 'react'
import { render } from 'react-dom'
import styles from './index.css'

class Button extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    console.log(styles)
    let buttonClass = ''
    switch (size) {
      case 'large':
        buttonClass = styles.large
        break

      case 'small':
        buttonClass = styles.small
        break

      default:
        buttonClass = styles.normall
        break
    }
    return (
      <button className={buttonClass}>确定</button>
    )
  }
}

export default Button

1.使用CSS Modules时发现,它只支持单独的class名字,不能像我们写css的时候一级一级的写,例如:.a .b .c,在CSS Modules中就是一步到位.c

2.CSS Modules提供了compose组合方法实现样式的复用,代码如下:

1
2
3
4
5
6
7
8
9
.font {
 line-height: 12px;
 font-size: 12px;
}

.title-font {
 composes: font;
 font-size: 24px;
}

但是有一个问题,我们在写样式的时候,需要频繁使用 styles.xxx,如何能够方便的直接写入class名字呢?可以使用React CSS Modules,它以高阶函数的形式生成className

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React, { Component } from 'react'
import { render } from 'react-dom'
import CSSModules from 'react-css-modules'
import styles from './index.css'

class Button extends Component {
  render() {
    return (
      <button styleName='normall'}>确定</button>
    )
  }
}

export default CSSModules(Button, styles)

可以看到,react-css-modules是运行时的依赖,而且需要在运行时获取className,性能损耗比较大,可否把获取className前置到编译阶段?答案是可以的,可以使用babel-plugin-react-css-modules

babel-plugin-react-css-modules插件可以实现用styleName属性自动加载CSS模块,通过babel插件来进行语法树解析并最终生成className,写的时候,只需要将我们原来写的className替换成styleName即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React, { Component } from 'react'
import { render } from 'react-dom'
import styles from './index.css'

class Button extends Component {
  render() {
    return (
      <button styleName='normall'}>确定</button>
    )
  }
}

export default Button

具体配置可以查看文档

https://github.com/webpack-contrib/css-loader

https://juanha.github.io/2017/01/10/react-css-modules/

http://www.alloyteam.com/2017/03/getting-started-with-css-modules-and-react-in-practice/

https://github.com/camsong/blog/issues/5