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 + '\n' + 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)

posted @ 2020-08-24 21:15  韩帅  阅读(6895)  评论(0编辑  收藏  举报