4

CSS Modules

为什么引入CSS Modules

(1)全局样式冲突
webpack进行打包时,将所有js文件导入到入口App.js文件中,样式也会统一加载到入口中,根据css的layout规则,后面的样式会覆盖掉前面的样式声明,造成全局样式的覆盖问题。
(2)嵌套层次过深的选择器
为了解决全局样式的冲突问题,不得不引入一些特地命名namespace来区分,但是往往有些namespace命名得不够清晰,就会造成要想下一个样式不会覆盖,就要再加一个新的命名空间来进行区分,最终可能一个元素的显示样式嵌套特别深。

嵌套特别深会造成的问题:
- 根据CSS选择器的解析规则可以知道,层级越深,比较的次数也就越多,影响整个页面的渲染
- 增加了不必要的字节开销
- 语义混乱 可扩展性不好,约束越多,扩展性越差

(3)无法共享变量
复杂组件要使用 JS 和 CSS 来共同处理样式

一些解决方案

(1)css预处理器(less/sass) 支持模块引入

存在问题:不能解决全局样式冲突问题

(2)BEM(Block Element Modifier)解决命名冲突以及更好的语义化

  • Block:逻辑和页面功能都独立的页面组件,是一个可复用单元,特点如下:

    • 可以随意嵌套组合
    • 可以放在任意页面的任何位置,不影响功能和外观
    • 可复用,界面可以有任意多个相同Block的实例
  • Element:Block的组成部分,依赖Block存在(出了Block就不能用)
  • [可选]定义Block和Element的外观及行为,就像HTML属性一样,能让同一种Block看起来不一样

  • 存在问题:对于嵌套过深的层次在命名上会给需要语义化体现的元素造成很大的困难 对于多人协作上,需要统一命名规范,这同样也会造成额外的effort

CSS Modules

(1)什么是css modules

通过构建工具来使指定class达到scope的过程

(2)css modules优势

  • 解决全局命名冲突问题 css modules只关心组件本身 命名唯一
  • 模块化 可以使用composes来引入自身模块中的样式以及另一个模块的样式
  • 解决嵌套层次过深的问题 使用扁平化的类名

(3)css modules的用法

  • 开启css modules功能
    由于css modules对样式的处理逻辑是放在loader中,所以在css-loader中开启css-loader

    {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: './build/styles'
            }
          },
          { 
            loader: "css-loader",
            options: {
                importLoaders: 1,
                modules: true,
                localIdentName: "[name]__[local]___[hash:base64:5]"  // 为了生成类名不是纯随机
            }
          }
        ]
    }        
  • 修改index.js文件中对样式的引入方式

    import styles from './index.css'; // 导出对象的格式是键值对 
  • 编译之后的类使用

    //index.css
    .color{
      color:blue;
    }
    
    // index.js
    import styles from './index.css'; // 导出对象的格式是键值对 
    
    // jsx
    <div class={styles.color}>使用转化后的类名</div> // 如果以下划线给类命名使用styles['color-style']

(4)其他用法

  • 局部变量和全局变量

    • :local 做localIdentName规则处理(类名进行编译 默认可以不写)
    • :global 样式编译后不变

      /*下面两种进行编译*/
      .normal {
       color: green;
       }
      :local(.normal) {
        color: green; 
      }
      
      /*下面两种不会进行编译*/
      // 多个类不编译
      :global {
        .link {
          color: green;
        }
        .box {
          color: yellow;
        }
      }
      // 单个类不编译
      :global(.box){
        color:blue;
      }
  • compose组合class(需要样式复用,在 CSS Modules 中,一个选择器可以继承另一个选择器的规则) 自身引入的声明的优先级会比较高

        /* 同一个css文件中复用类*/
        .bg {
            background-color:blue;
            }
        
        .title {
            composes:bg;
            color:white;
            }
        
       // js中使用
        import styles from './test.css';
        <div className={styles.title}>css modules </div>
    
      // 生成的HTML
        <div class="bg__h87YTC title__ji87h">css modules</div>
       /* 不同css文件中复用类*/ 
        /* color.css */
         .red {
           color: red;
          }
    
        .blue {
          color: blue;
        }
    
        /* index.css */
        .red {
            color: red;
        }
    
        .header {
            font-size: 32px;
        }
    
        .title {
            color: green;
            composes: blue from './color.css';
            border-bottom: 1px solid #ccc;
            padding-bottom: 20px;
        }    
  • CSS和JS变量共享(:export 关键字可以把 CSS 中的 变量输出到 JS 中)

        /* index.scss */
        $primary-color: #f40;
    
        :export {
          primaryColor: $primary-color;
        }
    
        /* app.js */
        import style from 'index.scss';
    
        // 会输出 #F40
        console.log(style.primaryColor);
    

备注

  • 项目中如果使用antd 需要将antd样式不进行编译
    使用exclude排除exclude中指定目录的资源。在node-modules中关闭css-module,在我们自己写的src下的css开启css-module

      {
        test: /\.css$/,
        exclude: /node_modules|antd\.css/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: './build/styles'
            }
          },
          { 
            loader: "css-loader",
            options: {
                importLoaders: 1,
                modules: true,
                localIdentName: "[name]__[local]___[hash:base64:5]"  // 为了生成类名不是纯随机
            }
          }
        ]
    }        
  • 项目中使用stylelint进行css检查 需要在stylelint配置忽略css-modules的关键字

     module.exports = {
      rules: {
        'selector-pseudo-class-no-unknown': [
          true,
          {
            ignorePseudoClasses: [
              'export',
              'import',
              'global',
              'local',
              'external',
            ],
          },
        ],
        'selector-type-no-unknown': [
          true,
          {
            ignoreTypes: ['from'],
          },
        ],
        'property-no-unknown': [
          true,
          {
            ignoreProperties: ['composes', 'compose-with'],
          },
        ],
        'at-rule-no-unknown': [
          true,
          {
            ignoreAtRules: ['value'],
          },
        ],
      },
    };

推荐css解决方案文章:
React拾遗:从10种现在流行的 CSS 解决方案谈谈我的最爱 (上)
CSS Modules入门及React中实践


喝冬瓜汤的丁小白
45 声望4 粉丝