zoukankan      html  css  js  c++  java
  • Vue中scoped属性浅析

    Scoped CSS(Vue Loader)

    在vue单文件组件中,为了防止全局同css类名名样式的污染,vue-loade对单文件组件 <style> 标签增加了scoped属性的处理。原理就是在html标签上添加data-v-xxxxxxxx属性,然后在css类名后添加属性选择器,即利用css类选择 + 属性选择器实现样式局部化:

    Parent.vue

    <template>
      <div class="parent">
        我是来自父组件的
      </div>
    </template>
    
    <style lang="scss" scoped>
      .parent {
        color: #333;
      }
    </style>
    

    转换结果:

    <template>
      <div data-v-2f3286d4 class="parent">
        我是来自父组件的
      </div>
    </template>
    
    <style>
      .parent[data-v-2f3286d4] {
        color: #333;
      }
    </style>
    

    使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

    我们将Parent.vue修改为:

    Parent.vue

    <template>
      <div class="parent">
        <Child></Child>
        我是来自父组件的
      </div>
    </template>
    
    <script>
      import Child from './Child'
    
      export default {
        name: 'Parent',
        components: {
          Child
        }
      }
    </script>
    

    新增Child.vue

    <template>
      <div class="child">
        <div>
          我是子组件默认色的
        </div>
    
        <div class="red">
          我是子组件红色的
        </div>
    
        <!--<div data-v-2f3286d4 class="red">-->
          <!--我是子组件被父组件编译过的红色的-->
        <!--</div>-->
      </div>
    </template>
    
    <script>
      export default {
        name: 'Child'
      }
    </script>
    
    <style lang="scss">
      .child {
        color: #999;
    
        .red {
          color: red;
        }
      }
    </style>
    

    下面看下如何在父组件中修改子组件中样式(两种情况):

    1. 父有scoped,子无scoped,这种情况也是常见的各种ui中的实现,每个ui组件中无scoped,我们在父组件中可以覆盖每个ui组件的默认样式。如上边提到的父组件加scoped后,在子组件的根节点会加入data-v-2f3286d4,我们在父组件直接这样写是没用的:
    <style lang="scss" scoped>
      .parent {
        color: #333;
        .red {
          color: greenyellow;
        }
      }
    </style>
    

    效果:

    以上直接修改的话,会被编译为:

    <style lang="scss" scoped>
      .parent .red[data-v-2f3286d4] {
          color: greenyellow;
      }
    </style>
    

    子组件中red选择的标签是没有 data-v-2f3286d4 属性的,但是我们可以在子组件中打开注释测试下:

    <div data-v-2f3286d4 class="red">
      我是子组件被父组件编译过的红色的
    </div>
    

    效果如下:

    我们需要加 /deep/ 或者 >>> 或者 ::v-deep 来修改子组件中 .red 的样式(会在 deep 使用后的class编译为 `[data-v-xxxxxxxx] .red形式),我们继续对之前打开注释的子组件进行注释,并修改父组件为:

    <style lang="scss" scoped>
      .parent {
        color: #333;
        /deep/ .red {
          color: greenyellow;
        }
      }
    </style>
    

    上边会被编译为

    .parent[data-v-2f3286d4] .red {
        color: greenyellow;
    }
    

    效果:

    1. 父有scoped,子有scoped,这种情况下大多出现在我们自己的公共组件中,这种方式并不推荐,我们写的公共组件应该不含有 scoped。参照 1 中提到的穿透组件写法我们出现的结果如下:

    出现这种问题的原因就是属性选择器权重 > class选择器权重,解决的方法就是需要提高父组件中覆盖样式的权重,方法很简单:加 !important。。。

    <style lang="scss" scoped>
      .parent {
        color: #333;
        /deep/ .red {
          color: greenyellow !important;
        }
      }
    </style>
    

    效果:

    data-v-xxxxxxxx生成规则

    我们看到标签上新增属性,可能有些小伙伴会好奇data-v-xxxxxxxx中的xxxxxxxx是如何生成的,我查阅vue loader的仓库中搜索发现:是否生产环境 ? hash(组件的内容) : hash(组件的相对路径):

    const moduleId = 'data-v-' + hash(isProduction ? content : shortFilePath)****
    

    后来有大佬发现,只根据内容有可能会是生成hash值相同,比如以下方式声明组件, issue地址

    <style lang="sass" src="./index.sass" scoped></style>
    <script lang="ts" src="./index.ts"></script>
    <template lang="pug" src="./index.pug"></template>
    

    后来就改为 内容 + 相对路径 生成hash, commit地址:

    const moduleId = 'data-v-' + hash(isProduction ? (shortFilePath + '
    ' + content) : shortFilePath)
    

    scoped总结

    添加了属性选择器,对于CSS选择器的权重加重了
    即使加入属性选择器,但是clsss还是没变,如果全局还存在同class名称的样式,还是有可能出现覆盖,比如用在 App.vue 中也定义了 .red 样式:

    效果:

    推荐使用 CSS Modules,我们直接生成一个唯一的class名,既保证了class的全局唯一(无法造成class名样式污染),有没有提高class选择器权重的增加

    参考:

    你知道style加scoped属性的用途和原理吗?
    Scoped CSS
    CSS Modules
    New scoped ID generation since v13.4 may cause duplicated ID (needs option to disable)

  • 相关阅读:
    .NET 正则表达式使用高级技巧之替换类介绍
    道法术器势
    JS函数匿名替换
    批量更改数据库表架构(生成sql后直接执行!)
    转: 从现实生活中理解什么是广播机制
    public View getView(int position, View convertView, final ViewGroup parent)三个参数的意思
    Android Intent个人介绍
    WPF中Timer与DispatcherTimer类的区别
    C# 使用ManualResetEvent 进行线程同步
    C# 使用AutoResetEvent进行线程同步
  • 原文地址:https://www.cnblogs.com/hanshuai/p/13555225.html
Copyright © 2011-2022 走看看