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