关于react.js:一种比cssscoped和cssmodule更优雅的避免css命名冲突小妙招

css_scoped 与 css_module

咱们晓得,简略的class名称容易造成css命名反复,比方你定义一个class:

<style>
.main { float: left; }
</style>

如果他人刚好也定义了一个className:.main,你的float:left就会影响到它。

所以Vue中创造了css_scoped,其原理就是在class名称后加上一个data属性选择器:

<style scoped>
.main { float: left; } 
</style>

//本义后变成
<style>
.main[data-v-49729759] { float: left }
</style>

css_scoped是Vue的专用计划,如果你应用React等其它UI框架,那么你能够应用更通用的css_module,其原理是为款式名加hash字符串后缀,从而保障class名全局惟一:

<style module>
.main { float: left; } 
</style>

//本义后变成
<style>
.main_3FI3s6uz { float: left; } 
</style>

相比于css_scopedcss_module计划更通用,不扭转其自身的权重,而且渲染性能要比前者好很多,所以更举荐大家应用css_module

不足之处

然而不论是css_scoped还是css_module,都绕不开2大毛病:

  1. 因为加上了随机字符,所以如果想在父组件中笼罩子组件中的款式变得麻烦,尽管css_scoped能够应用穿透,但这样容易引发别的问题。
  2. 加上随机字符让class名称变得不优雅,也影响编译速度。

css命名空间

咱们来回顾一下,在css_scopedcss_module呈现之前,人们是如何防止css命名抵触的?

对,就是人为的定义一些css命名空间

那个时候,对每个Component组件都会在其根节点上定义一个不反复的ID或者class作为其命名空间,而后其外部的其它class都会以此命名空间作为前置限定,比方:

<div class="table-list">
    <div class="hd"></div>
    <div class="bd"></div>
    <div class="ft"></div>
</div>

<style>
.table-list {
    > .hd {
        color: red
    }
    > .bd {
        color: blue
    }
} 
</style>

这样一来,只有保障根节点的class不反复,其子节点的class就不会反复。

而对于一些全局款式,人们习惯加上一个g-作为命名空间,比方:

<style>
.g-hd {
    color: red
} 
</style>

这种依附人为约定的css命名空间,尽管比拟原始,但有其长处:

  • 简略无效,按模块-组件名称的命名约定,基本上很容易保障其不反复。
  • 款式名更具语义,从任何一个dom登程,向上肯定能找到其组件根节点class名,基本上就能猜到其组件所在的业务模块、组件地位等。
  • 父组件很容易利用权重笼罩子组件的任何款式。

css_namespace + css_module

如果咱们把css_modulecss_命名空间联合起来,组件的命名空间由css_module主动生成,那岂不是一种更优雅的解决css抵触的计划么?

css_module中有2个特地的作用域限定符:

  • :global 该限定符下的class名称将放弃原样,不会被css moudle转换,比方:

    :global { 
        .test1 { color: blue; } 
        .test2 { color: red; } 
    }
    //编译后
    .test1 { color: blue; }
    .test2 { color: red; } 
  • :local 该限定符下的class名称,将会被css moudle转换,比方:

      :local { 
          .test1 { color: blue; } 
          .test2 { color: red; } 
      }
      //编译后
      .test1_3zyde4l1y { color: blue; }
      .test2_2DHwuiHWM { color: red; } 

    如果咱们应用css_namespace + css_module

    <div :class="styles.root">
      <div class="hd"></div>
      <div class="bd"></div>
      <div class="ft"></div>
    </div>
    
    <style module>
    :global {
    :local(.root) {
      > .hd { 
       color: red;
       .title {
           font-size: 18px;
       }
      }
      > .bd { color: blue; }
    }
    }
    </style>
    
    //css编译后
    <style>
    .root_3zyde4l1y > .hd{ color: red; }
    .root_3zyde4l1y > .hd .title{ font-size: 18px; }
    .root_3zyde4l1y > .bd{ color: blue; }
    </style>

    这样的意思是:

  • 每个组件原则上仅根节点应用css_module主动生成不反复的class名称,其余外部元素放弃原始命名,不做任何转换。(当然某些状况下,也能够应用多个转换)
  • 为了保障孙子辈款式不影响他人,能够适当退出dom层级限定,比方> .hd这样就只会影响子级的.hd

去除css_moudle随机字符

<style>
.root_3zyde4l1y > .hd{ color: red; }
.root_3zyde4l1y > .hd .title{ font-size: 18px; }
.root_3zyde4l1y > .bd{ color: blue; }
</style>

根节点上的class命名带个hash小尾巴,依然很不优雅。其实hash字符只是为了保障这个名称全局惟一而已,你也能够应用另外的办法来保障。如果你为工程设计一个有意义的目录构造,那么齐全能够应用目录门路来代替hash字符串,比方你的工程目录如下:

src
├── components
│    ├── moduleA
│    │     ├── componentX
│    │     ├── componentY
│    ├── moduleB
│    │     ├── componentZ

那么:components-moduleA-componentX这个目录门路肯定是全局惟一的,所以你能够应用这个门路来代替hash字符,css_module提供了自定义转换className的办法:

type getLocalIdent = (
        context: LoaderContext,
        localIdentName: string,
        localName: string
) : string;

你能够通过该办法来将目录门路映射为class名称,并替换掉一些固定的目录,比方工程目录如下:

src
├── assets
│     ├── css
│           ├── global.module.scss //全局款式
│                  ├── :local(.loading) {} //全局款式只须要加个g-前缀,编译成.g-loading
├── components
│     ├── NavBar
│           ├── index.module.scss
│                  ├── :local(.root) {} //依据目录门路可编译成即可.comp-NavBar
│
├── modules
│     ├── user
│           ├── components
│                 ├── LoginForm
│                         ├── index.module.scss
│                               ├── :local(.root) {} //依据目录门路可编译成.user-LoginForm,
│

留神的是src/modules/user/components/LoginForm/index.module.scss,依据目录门路能够生成:modules-user-components-LoginForm,但因为user是一个module,其名称是惟一的,且内部结构遵循约定,所以能够简化为:user-LoginForm

依据class名称揣测文件地位

  • .g-loading – 带g-前缀,阐明它是一个全局class,对应的文件肯定是src/assets/css/global.module.scss
  • .comp-NavBar – 带comp-前缀,阐明它是一个公共组件,对应的组件肯定是src/components/NavBar
  • .user-LoginForm – 依据约定,对应的组件肯定是src/modules/user/components/LoginForm

示例及源码

如果你也应用相似的工程目录,那么能够间接应用我封装好了的门路映射函数getCssScopedName

const {getCssScopedName} = require('@elux/cli-utils');
const srcPath = path.resolve(__dirname, '../src');

// webpack css-loader
{
    loader: 'css-loader',
    options: {
      importLoaders: 2,
      modules: {
        getLocalIdent: (context, localIdentName, localName) => {
          return getCssScopedName(srcPath, localName, context.resourcePath);
        },
        localIdentContext: srcPath,
      },
    },
  };

当然你也可本人实现个性化的getLocalIdent,无非就是一些正则匹配与替换罢了…

采纳css_namespace + css_module的理论案例:

  • 基于Antd的后盾管理系统
  • 或者应用任意一个elux工程模版:npm create elux@latest 或 yarn create elux

如图所示,通过class名称基本上就能揣测出组件地位…

欢送交换,自己近期文章:

  • 前端架构-分层而治,铁打的MV流水的C
  • 手撸Router,还要啥Router框架?让react-router/vue-router躺一边凉爽去
  • 不想当Window的Dialog不是一个好Modal,弹窗翻身记

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理