害怕样式冲突?不如试试Css Module

81 阅读4分钟

前言

在公司中,一个项目可能会由几十个人进行数年的开发迭代,难免会有Css样式冲突问题。

而CSS Modules是一种用于管理组件样式的技术,它通过提供局部作用域的CSS规则,解决了全局样式冲突和命名空间问题。它的工作原理是将每个组件的样式规则封装到一个独立的作用域中,避免了样式的全局污染。我们比较常用的就是通过配置css-loader来为类名添加哈希,来保证样式的唯一性。

基础用法

css-loader

首先,Css Module模式的开启需要配置css-loader(详细可见www.webpackjs.com/loaders/css…

这里举一个最简单的例子:

// webpack.config.js
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
            options: {
              modules: true
            },
          },
        ],
      },
    ],
  },

css,jsx

css编写则跟通常写法没有什么变化。

// index.module.css
.wrapper{
  background: red;
  
}

.wrapper_text{
  color: #fff;
}

在jsx中,我们不再是仅仅简单的导入css文件了,而且将整个文件视为一个模块。

import style from './index.module.css'
function App(){
  return (
    <div className={style.wrapper}>
      <div className={style.wrapper_text}>app</div>
    </div>
  )
}

编译后我们可以在html style中看到:

image.png

最终我们的类名被哈希化了,而且这个是全局独一无二的,这样妈妈再也不用担心我们的样式命名会和别人冲突啦~

但是类名哈希化后可能不利于我们平时开发时进行debug,因此我们通过设置css-loader来达到编译后类名的自定义格式:

modules: {
// local: 原本的类名
  localIdentName: "[local]--[hash:base64:5]",
}

编译后效果:

image.png

现在编译后的类名是不是就可以对应上我们开发时候所设置的类名了呢?

需要注意的是,Css Module只能作用于类选择器和ID选择器,因为css-loader并不可能对普通标签选择器等作哈希化。

组合

Composition。在Css Module中,一个选择器可以组合其他选择器的规则。

// index.css
.child{
  display: flex;
  justify-content: center;
  align-items: center;
}
.composition{
  color: yellow;
  // 组合自child选择器
  composes: child;
}

如上我们可以通过composes关键字来达到选择器组合的效果,引入className的时候只需要引入style.composition,实际编译后效果如图所示:

image.png

注意:该功能只有css中才支持,目前我们一般用的scss中可通过&.xxx亦可实现同样的效果。个人感觉该功能比较鸡肋。

global伪类

当我们需要修改引入的第三方组件(或者非 CSS Modules 模块)的样式时,我们通常需要直接对其类名编写样式。然而,在 CSS Modules 中编写的类名会被哈希化,这意味着我们无法直接对第三方类名进行样式修改。这时,:global选择器就发挥了作用,它允许我们有选择地不对某些类名进行编译:

.child:global(.hello){
  display: flex;
  justify-content: center;
  align-items: center;
}

编译后结果如下:

image.png

通过在样式规则前加上 :global,我们告诉 CSS Modules 不对该类名进行哈希化,使其保持原样。可见.hello类名完好无损地保留了下来。这样,我们就能够对第三方类名应用样式修改,而不受 CSS Modules 的影响。

使用:global 选择器时要注意,它的作用范围是全局的,因此需要谨慎使用,以避免样式冲突和全局污染。我们应该尽量将样式的作用范围限制在组件内部,只在必要时使用 :global 来修改第三方组件的样式。

实际使用(Scss)

在实际项目开发中,Css Module在Scss中的表现又会如何呢?

注意,下面的box选择器并不能被编译成模块。因为具有伪类标签。

.box:global(.xxx) {
  // ...
}

global

在scss中,:global伪类的用法就更加简便易行。结合scss的嵌套式写法,我们可以通过:global{}将多个类名同时保留化。

.child:global(.hello){
  display: flex;
  justify-content: center;
  align-items: center;
}

.child{
  // :global下面的类名都不会被编译
  :global{
    .child_text{
      //...
    }
    .child_icon{
      
    }
  }
}

结合typescript

我们在引用css模块的时候,如果使用typescript的话,会没有类型声明。因此我们可以使用typings-for-css-modules-loader,它可以自动在编译时生成对应的.d.ts文件。

typings-for-css-modules-loader有点坑,只支持css-loader@1版本,即webpack4,亲测可以使用@teamsupercell/typings-for-css-modules-loader

结语

使用CSS Modules,我们可以在React或其他前端框架中,通过导入样式文件并将其绑定到组件上,实现样式与组件的一对一关系。这样,每个组件的样式规则都只对当前组件生效,不会影响其他组件。