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

6次阅读

共计 4030 个字符,预计需要花费 11 分钟才能阅读完成。

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,弹窗翻身记
正文完
 0