在日常 Vue 开发中,经常会遇到样式穿透不生效的情况。如果是选择器优先级问题或者穿透写法问题,这种比较好理解,提升优先级调整写法就好,但在 Vue 中有时候需要将 scoped 去掉才能生效,有时候又不能去掉 scoped,有时候因为写法的问题导致不生效,现整理下具体原因,以防后续踩坑。
scoped 与样式穿透的爱恨情仇
1)scoped 的作用:
scoped 的作用是避免样式污染,不加 scoped,书写的样式作用于全局,加之后的样式仅针对当前组件生效。
2)scoped 的原理:
每个添加了 scoped 的组件会被分配一个唯一哈希,通过 CSS 属性选择器实现域划分。
3)demo 示例:
<!-- App.vue -->
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>
<!-- layout.vue -->
<template>
<div class='AdminPage'>
<div class='app-header-box'>
<AppHeader/>
</div>
<div class='app-content-box'>
</div>
</div>
</template>
<style scoped>
.AdminPage{
position: relative;
width: 100%;
background-color: #F3F2F2;
}
.AdminPage .app-header-box{
position: fixed;
width: 100%;
height: 48px;
z-index: 100;
}
.AdminPage .app-content-box{
}
</style>
添加了 scoped 的组件,最终渲染效果:
.每个添加了 scoped 的组件渲染出来都会附带一个唯一的属性 data-v-xxx;
.一个组件中的所有标签都会带上同样的 data-v-xxx 属性;
.子组件会带上父组件的 data-v-xxx 属性;
.在使用第三方的 UI 库时,只会为根元素添加 data-v-xxx 属性,子元素中则不会添加;
4)demo 效果:
deep() 做了哪些工作?
deep() 函数会把属性选择器放在最前面。
为什么有时候穿透需要多包裹一层容器
1)demo 示例:
father.vue 嵌套子组件 son.vue
<!-- father.vue -->
<template>
<div>father</div>
<Son />
</template>
<script setup>
import Son from "./Son.vue"
</script>
<style lang="less" scoped>
div{
color: red;
}
:deep(.second-row){
color: yellow;
}
</style>
<!-- son.vue -->
<template>
<div>111</div>
<div class="second-row">222</div>
<div>333</div>
</template>
<script></script>
<style scoped>
.second-row{
color: green
}
</style>
2)demo效果:
按照上述代码,穿透样式并未生效,仅子组件样式生效。

这是因为并没有生成嵌套关系,父组件中由于添加 scoped 注册的样式穿透是针对 date-v-father 这一前置条件下的,即:
[data-v-c61e7f05] .second-row {
color: blue;
}
但是,目前的 DOM 结构下 data-v-c61e7f05 下面并没有 second-row 的类名。所以需要调整,父组件使用一个容器包裹起来,完成嵌套,即:
<!-- father.vue -->
<template>
<div class="wrapper">
<div>father</div>
<Son />
</div>
</template>
<script setup>
import Son from "./Son.vue"
</script>
<style lang="less" scoped>
div {
color: red;
}
:deep(.second-row) {
color: blue;
}
</style>
去掉 scoped 样式穿透有时生效或不生效?
主要有3个原因:
1.去掉 scoped 样式就变成全局样式了
2.添加 :deep() 是把其属性选择器放在最前面
3.第三方 UI 组件只会为根元素添加 data-v-xxx 属性
案例分析
<!-- father.vue -->
<template>
<div>
<div>father</div>
<Son />
</div>
</template>
<script setup>
import Son from "./Son.vue"
</script>
<style scoped>
div{
color: red;
}
:deep(.second-row){
color: blue;
}
</style>
<!-- son.vue -->
<template>
<div>111</div>
<div class="second-row">222</div>
<div>333</div>
</template>
<script></script>
<style scoped>
.second-row{
color: green
}
</style>
1)添加scoped,使用穿透,样式穿透生效:

2)移除scoped,使用穿透,样式穿透未生效:
由于没有了属性, :deep作用.second-row 没有属性可提,子组件定义样式生效,“穿透无效”。
3)添加scoped,不使用穿透,子组件样式生效:
父组件定义的样式带了属性,但优先级没有子组件定义的高【按照选择器的计算是优先级相同的,是因为子组件样式后加载?】

注意:这里如果子组件移除掉scoped,子组件的优先级降低,就是父组件样式生效。
4)不添加scoped,不使用穿透,子组件样式生效:
父组件定义的是全局,但优先级没有子组件定义的高。
样式穿透的一些写法
写法有:::v-deep,>>>,:deep(),/deep/,具体使用如下:
1.如果你使用的是 css,没有使用 css预处理器,则可以使用 >>>,/deep/,::v-deep。
2.如果你使用的是 less 或者 node-sass,那么可以使用 /deep/,::v-deep 都可以生效。
3.如果你使用的是 dart-sass,那么就不能使用 /deep/,而是使用 ::v-deep 才会生效。
4.但是如果你是使用 vue2.7 以上版本以及包括 vue3,::v-deep也会生效,但是会有警告