如何正确使用scoped和deep

2026/1/26 vue3

# 前言

其实写过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属性

alt text

# 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]

alt text

# 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类的元素样式都能应用生效

alt text

# 2. 不可生效写法

# 2.1 使用了scoped,但没有使用deep

其实就是前面1.2和1.3讲的那样,即不赘述了

常见的情况就是子组件是antd、element-ui之类的第三方库的组件,直接修改样式不生效,需要用到deep

alt text

# 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)这样的选择器,自然就不会生效了 alt text

# 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-bluecolor-green都不能生效

因为前面说到,deep的写法,是将应用的选择器前,加上该组件的属性作为后代选择器

应用在最外层元素的选择器上,就是[data-v-7a7a37b1] .wrapper

但已经是最外层的元素了,没有父类再有data-v-7a7a37b1属性了,故[data-v-7a7a37b1].wrapper是兄弟关系,后代选择器无法生效

alt text

解决:像这样再包一层,别用在最外层即可

<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>

alt text

# 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]

alt text

所以这种情况,只能去掉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>

alt text

而实际开发中,通常是像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的时候也要具有独特性,避免重复影响其他组件样式

上次更新: 2026/2/3 09:23:52
CHANGE THE WORLD
v6