如何正确使用scoped和deep
# 前言
其实写过vue的前端开发,基本上vue的代码片段模板是默认有scoped,一般不会太留心,发现子组件样式不生效就用deep
基本上加了deep就能解决问题,但有时加了样式却不生效如后文2.3,也不去探其究竟,就将scoped和deep都去掉了,迅速地解决问题
但这样的写法还是有隐患的,所以本文就详细讲讲如何正确使用scoped和deep
# 1. 可生效写法
# 1.1 未用scoped
<!-- 父组件 -->
<script setup>
import TestChild from './components/TestChild.vue'
</script>
<template>
<div class="wrapper">
<div class="color-blue">父组件元素</div>
<TestChild />
</div>
</template>
<style>
.color-blue {
color: blue;
}
</style>
<!-- 子组件TestChild -->
<template>
<div>
<div class="color-red">TestChild子组件元素</div>
<div class="color-blue">子组件元素2</div>
</div>
</template>
<script setup lang='ts'>
</script>
<style>
.color-red {
color: red;
}
</style>
就是正常的样式,也没有data属性

# 1.2 用了scoped
<style scoped>
.color-blue {
color: blue;
}
</style>
<style scoped>
.color-red {
color: red;
}
</style>
用了scoped,当前组件中所有元素都会自动加上data-v-xxx属性,父组件为data-v-7a7a37b1,子组件为data-v-0c4b2325
还有可以看到子组件最外层元素,同时有data-v-7a7a37b1和data-v-0c4b2325
scoped中的样式也会加上相应的属性选择器,如图.color-blue[data-v-7a7a37b1]、.color-red[data-v-0c4b2325]

# 1.3 用了scoped和deep
如1.2所示,用了scoped后,选择器加上了属性选定,所以即使子组件元素2有color-blue类,样式也不能生效,因为选择器是.color-blue[data-v-7a7a37b1](无论子组件有无scoped,主要是样式写在有scoped的父组件上)
要想其生效,就需要用deep
<style scoped>
:deep(.color-blue) {
color: blue;
}
</style>
加上deep后,选择器由.color-blue[data-v-7a7a37b1] 变为了 [data-v-7a7a37b1] .color-blue
父组件的属性,作为后代选择器在.color-blue前,父组件最外层元素就有该data-v-7a7a37b1属性,所以该页面所有元素,包括父组件内元素和子组件内元素,有color-blue类的元素样式都能应用生效

# 2. 不可生效写法
# 2.1 使用了scoped,但没有使用deep
其实就是前面1.2和1.3讲的那样,即不赘述了
常见的情况就是子组件是antd、element-ui之类的第三方库的组件,直接修改样式不生效,需要用到deep

# 2.2 没有使用scoped,但是使用了deep
<!-- 父组件 -->
<script setup>
import TestChild from './components/TestChild.vue'
</script>
<template>
<div class="wrapper">
<div class="color-blue">父组件元素</div>
<TestChild />
</div>
</template>
<style>
:deep(.color-blue) {
color: blue;
}
</style>
这样就没有办法成功编译,css没有:deep(.color-blue)这样的选择器,自然就不会生效了

# 2.3 deep用在最外层元素上
经常有多个子组件需要穿透修改样式,其类名不一样,不想一个个用deep,就会在子组件外包裹一层,在父元素用一个deep,这样就都能生效了,因为是如1.3那样后代选择器,后代都能生效
但是若是deep用在最外层元素上,就会有问题了
<!-- 父组件 -->
<script setup>
import TestChild from './components/TestChild.vue'
</script>
<template>
<div class="wrapper">
<div class="color-blue">父组件元素</div>
<TestChild />
</div>
</template>
<style scoped>
:deep(.wrapper){
.color-blue {
color: blue;
}
.color-green {
color: green;
}
}
</style>
<!-- 子组件TestChild -->
<template>
<div>
<div class="color-red">TestChild子组件元素</div>
<div class="color-blue">子组件元素2</div>
<div class="color-green">子组件元素3</div>
</div>
</template>
<script setup lang='ts'>
</script>
<style scoped>
.color-red {
color: red;
}
</style>
color-blue和color-green都不能生效
因为前面说到,deep的写法,是将应用的选择器前,加上该组件的属性作为后代选择器
应用在最外层元素的选择器上,就是[data-v-7a7a37b1] .wrapper
但已经是最外层的元素了,没有父类再有data-v-7a7a37b1属性了,故[data-v-7a7a37b1]和.wrapper是兄弟关系,后代选择器无法生效

解决:像这样再包一层,别用在最外层即可
<script setup>
import TestChild from './components/TestChild.vue'
</script>
<template>
<div class="wrapper">
<div class="inner">
<div class="color-blue">父组件元素</div>
<TestChild />
</div>
</div>
</template>
<style scoped>
:deep(.inner){
.color-blue {
color: blue;
}
.color-green {
color: green;
}
}
</style>

# 2.4 子组件元素在父组件外
<!-- 父组件 -->
<script setup>
import TestChild from './components/TestChild.vue'
</script>
<template>
<div class="wrapper">
<div class="inner">
<div class="color-blue">父组件元素</div>
<TestChild />
</div>
</div>
</template>
<style scoped>
:deep(.color-blue) {
color: blue;
}
</style>
<!-- 子组件TestChild -->
<template>
<div>
<div class="color-red">TestChild子组件元素</div>
<Teleport to="body">
<div class="color-blue">子组件元素2Teleport到body中</div>
</Teleport>
</div>
</template>
<script setup lang='ts'>
</script>
<style scoped>
.color-red {
color: red;
}
</style>
子组件在父组件中,但其元素却在父组件元素外,用了scoped进行了样式隔离,加deep也没用,因为选择器为[data-v-7a7a37b1] .color-blue,而元素在外,并不在[data-v-7a7a37b1]内

所以这种情况,只能去掉scoped,但又不想污染全局,就再加一个独有的class即可,如下
<!-- 父组件 -->
<script setup>
import TestChild from './components/TestChild.vue'
</script>
<template>
<div class="wrapper">
<div class="inner">
<div class="color-blue">父组件元素</div>
<TestChild bodyChildClass="test-body-child" />
</div>
</div>
</template>
<style scoped>
:deep(.color-blue) {
color: blue;
}
</style>
<style>
.test-body-child {
background-color: blue;
}
</style>
<!-- 子组件TestChild -->
<template>
<div>
<div class="color-red">TestChild子组件元素</div>
<Teleport to="body">
<div class="color-blue" :class="bodyChildClass">子组件元素2Teleport到body中</div>
</Teleport>
</div>
</template>
<script setup lang='ts'>
defineProps<{
bodyChildClass?: string
}>()
</script>
<style scoped>
.color-red {
color: red;
}
</style>

而实际开发中,通常是像antdv、elementUI一些弹出层,会挂载在body中,其提供如popupClassName属性,就类似于上面的做法
# 总结
综上可以看出scoped和deep的原理和作用:
scoped起到一个样式隔离的作用,加个data-v-xxx属性,让当前组件的选择器更精细准确地应用,不污染其他组件
deep的作用是在有scoped的情况下,当前组件的data-v-xxx属性和deep选中的选择器,组成后代选择器,以实现穿透,让子组件选择器能应用,样式能生效的作用。
其他要点:
需注意若无scoped,deep不作编译不生效
若是deep应用在最外层元素上也不生效
另外,deep写法很多,但/deep/、:deep、:deep{}用法废弃了,一些环境下这些样式虽然生效但是不规范,应该改成:deep(<inner-selector>),用括号括起来的方式
若是实在不能用scoped,定义class的时候也要具有独特性,避免重复影响其他组件样式
