• 一种比css_scoped和css_module更优雅的避免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的实际案例:

    111.jpg

    如图所示,通过class名称基本上就能推测出组件位置...

  • 相关阅读:
    异常篇—— VEH 与 SEH
    如何成为一名高级数字 IC 设计工程师(1-7)Verilog 编码语法篇:常数
    国内首批!阿里云云原生数据湖产品通过信通院评测认证
    PMOS阵列(PMOS阵列代替)(高侧开关)
    web二级操作题
    MyBatis实现多层级collection嵌套查询
    加密算法、哈希算法及其区别+国密简介
    活动选择问题
    【学习】渗透测试有哪些重要性
    计算机网络 | 数据链路层
  • 原文地址:https://www.cnblogs.com/hiisea/p/16645688.html