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,弹窗翻身记