文章目录
- [Vue scoped 样式不生效的一个坑:CSS 选择器与 class 合并机制](#Vue scoped 样式不生效的一个坑:CSS 选择器与 class 合并机制)
-
- 1.相关知识
-
- [1.1 后代选择器 vs 同元素多 class 选择器](#1.1 后代选择器 vs 同元素多 class 选择器)
- [1.2 Vue class 合并机制](#1.2 Vue class 合并机制)
- [1.3 scoped + :deep 穿透原理](#1.3 scoped + :deep 穿透原理)
- [1.4 小结:空格 vs 无空格](#1.4 小结:空格 vs 无空格)
- [2. demo实战](#2. demo实战)
Vue scoped 样式不生效的一个坑:CSS 选择器与 class 合并机制
1.相关知识
1.1 后代选择器 vs 同元素多 class 选择器
后代选择器
javascript
.a .b
意思:选中 a 里面的 b
结构如下:
javascript
<div class="a">
<div class="b"></div>
</div>
同元素多 class 选择器
.a.b
选中同时具有 a 和 b 的元素
结构如下:
javascript
<div class="a b"></div>
一个空格,修改样式的含义就改变了
1.2 Vue class 合并机制
Vue 会把父组件传入的 class 合并到子组件根元素
代码例子如下:
javascript
父组件
<ChunkUploadTrigger class="upload-area" />
子组件:
<div class="chunk-upload-trigger">
最终 DOM:
<div class="chunk-upload-trigger upload-area">
1.3 scoped + :deep 穿透原理
Vue scoped CSS 穿透
当写
javascript
<style scoped>
.box {
color: red;
}
</style>
Vue 编译后会变成类似:
<div class="box" data-v-123abc>
CSS 也会变成:
.box[data-v-123abc] {
color: red;
}
CSS 只会影响 当前组件。
举例来说:
子组件 Child.vue
javascript
<template>
<div class="child-box">
子组件内容
</div>
</template>
<style scoped>
.child-box {
border: 2px solid gray;
padding: 20px;
}
</style>
父组件 Parent.vue
javascript
<template>
<Child />
</template>
<script setup>
import Child from "./Child.vue"
</script>
<style scoped>
.child-box {
border-color: red;
}
</style>
样式不会生效
原因如下:
编译后 DOM 变成:
javascript
<div class="child-box" data-v-child></div>
父组件 CSS:
javascript
.child-box[data-v-parent]
但是 DOM 是:
javascript
data-v-child
不是:
data-v-parent 所以匹配不到
正确写法:
javascript
<style scoped>
:deep(.child-box) {
border-color: red;
}
</style>
// 忽略 scoped 限制
// 直接选中 child-box
1.4 小结:空格 vs 无空格
来一个例子:
javascript
.upload-area :deep(.chunk-upload-trigger)
//这在 CSS 里的意思是:
upload-area 里面的 chunk-upload-trigger
也就是要求 Dom
javascript
<div class="upload-area">
<div class="chunk-upload-trigger"></div>
</div>
但是如果 Dom 结构是下面这样的
javascript
<div class="chunk-upload-trigger upload-area"></div>
意思是同时拥有两个 class 的元素
javascript
:deep(.chunk-upload-trigger.upload-area)
没有空格
空格 = 父子关系,没空格 = 同一个元素
2. demo实战
子组件:
html
<template>
<div class="child-box">
子组件
<div class="content">
内容区域
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
.child-box {
border: 2px solid #ccc;
padding: 20px;
}
.content {
margin-top: 10px;
padding: 10px;
background: #f5f5f5;
}
</style>
子组件DOM实际是:
html
<div class="child-box">
子组件
<div class="content"></div>
</div>
父组件 Parent.vue
html
<template>
<div class="wrapper">
<h3>案例1:class 透传</h3>
<Child class="a b" />
</div>
</template>
<script setup>
import Child from "./Child.vue"
</script>
<style scoped>
</style>
Vue 会把 class 合并到子组件根节点。
此时真实的DOM
javascript
<div class="child-box a b">
子组件
<div class="content"></div>
</div>
练习1:.a.b(同一个元素)
在 Parent.vue 写:
css
:deep(.a.b) {
border: 3px solid red;
}

练习2:.a .b(后代选择器)
修改 Parent.vue:
html
<template>
<div class="wrapper">
<div class="a">
<Child class="b" />
</div>
</div>
</template>
真实 DOM:
html
<div class="a">
<div class="child-box b"></div>
</div>
推荐写法:
javascript
.a :deep(.b) {
border: 3px solid blue;
}
含义
javascript
当前组件中的 .a
↓
穿透到子组件中的 .b
Vue 编译后的效果类似:
.a[data-v-xxxx] .b
- .a 仍然受 scoped 限制
- .b 允许 穿透到子组件
这种写法同样可以生效,但作用范围更大。
javascript
:deep(.a .b){
border: 3px solid red;
}
-
.a 和.b 都脱离了 scoped 约束。
-
因此只要在当前组件 DOM 树内部出现 .a .b 结构,就可能匹配到。这会让样式的 影响范围变大,也更难控制。