Vue scoped属性和深度作用选择器

2,463 阅读3分钟

在编写 Vue 组件的时候,为了避免样式污染,我们一般会给 <style> 标签增加 scoped 属性,但我们也经常会遇到需要修改子组件样式的情况,比如 Element UI 。下面我们就来了解一下 scoped 属性以及深度作用选择器。

scoped 属性

scoped 是通过使用 PostCSS 来进行转换,给 DOM 节点增加一个 data-v-xxx 的唯一属性,再利用 CSS 的属性选择器,来达到样式隔离的效果。

<style scoped> 
  .a {
    color: red;
  }
</style>

// 编译后
.a[data-v-xxx] {
    color: red;
}

data-v-xxx 生成是根据 相对路径 + 内容 进行生成的。(commit

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

通过查阅官方文档,我们也得到如下信息:

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

所以在使用 scoped 属性后,父组件只能修改子组件根节点样式,那么怎样才能修改更深层级的子元素呢?

深度作用选择器

深度作用选择器的目的,就是为了修改更深层级的子元素样式。

我们可以使用 >>> 操作符

<style scoped> 
  .a >>> .b {
    color: red;
  }
</style>

上面代码会被编译为 .a[data-v-xxx] .b  

但是在 Sass 之类的预处理器无法正确解析 >>>(下面代码无法生效)

// 不生效
<style lang="scss" scoped> 
  .a >>> .b {
    color: red;
  }
</style>

这种情况下需要使用 /deep/::v-deep ,结果也是被编译为 .a[data-v-xxx] .b

<style lang="scss" scoped> 
  .a /deep/ .b {
    color: red;
  }
</style>

<style lang="scss" scoped> 
  .a ::v-deep .b {
    color: red;
  }
</style>

需要注意的是,多个>>>/deep/::v-deep 操作符,只有最外层才会生效,后面的操作符不会进行处理。

<style>
    .a >>> .b >>> span{
    color:red;
    }
</style>

// 渲染结果
.a[data-v-xxx] .b >>> span{
    color:red;
}

注意事项

但是,并不是所有的操作符都是爸爸的好孩子,其中有些是已经被官方弃用的,根据尤雨溪2020年9月的这篇文章,我们大概可以了解到:

  1. 最初是使用 >>> 来达到 "deep" 的效果,但是某些 CSS 预处理器不支持
  2. 后来选择了 /deep/,它曾经是 CSS 的一个真正的提议(甚至在 Chrome 中自带),但是后来被放弃了。所以为了避免用户困惑,最后使用了 ::v-deep(加了个 v,证明是我 Vue 的纯正血统)
  3. 然后 Vue 3 来了,我们可以抛弃历史包袱了,所以在 Vue 3 中,我们不再支持 >>>/deep/ ,推荐大家使用 ::v-deep,而且为了更加符合 CSS 的书写习惯,希望大家使用 ​::v-deep(.class)​ 的书写规则
  4. 在 Vue 3 中还提供了 ::v-slotted::v-global 两种新的操作符,针对 <slot> 和全局 CSS 规则
  5.  ​::v-slotted​ 编译之后的属性值为 ​data-v-xxx-s​,-s 的后缀使得它只针对 <slot> 内容
  6. ::v-global​ 编译之后则不带 ​data-v-xxx​ 的属性

总结

scoped 是为了避免样式污染而添加的属性,原理是通过给 DOM 增加 data-v-xxx 的唯一属性,再通过 CSS 属性选择器来达到效果。

如果遇到需要更改深层元素样式的情况,我们可以使用深度作用选择器,但需要区分版本和使用场景,大致如下:

Vue 2

  • 不推荐使用 /deep/
  • 在 Sass 之类的预处理器中使用 ::v-deep
  • 没有预处理器的情况下使用 >>>
  • 使用上面的操作符,<style> 必须有 scoped 属性

Vue 3

  • 不支持 /deep/
  • 不支持 >>>
  • 推荐使用 ::v-deep(.class) 代替 ::v-deep .class
  • 针对 <slot> 可以使用 ::v-slotted 选择器
  • 可以使用 ::v-global 注册全局样式
  • 使用上面的操作符,<style> 必须有 scoped 属性

参考

Vue Loader

scoped-styles-changes