【样式穿透】VUE样式穿透为啥有时不生效,把ta嚼烂

9,833 阅读5分钟

背景:经常在UI框架进行样式穿透的时候,会发生不生效的情况,既会是因为选择器优先级问题或者穿透写法问题,这种比较好理解,提升优先级调整写法就好,但在vue中有时候需要将scoped去掉才能生效,有时候又不能去掉scoped,有时候因为写法的问题导致不生效【需要用一个大盒子包裹】,整理下具体原因,以后遇到其他场景再逐步完善。

省流核心遵旨:

  1. 去掉scoped样式就变成全局样式了
  2. 添加:deep()是把其属性选择器放在最前面
  3. 第三方UI组件只会为根元素添加 data-v-xxx 属性
  4. 包一层大盒子实现属性嵌套

scoped与样式穿透的爱恨情仇

1. scoped的作用以及原理

作用:避免样式污染

不加scoped,书写的样式作用于全局,加了的话样式仅针对当前组件生效

原理:

每个配置了scoped的组件分配一个唯一哈希,通过css属性选择器实现域划分

先看案例:

// 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的组件,最终渲染效果:

(1) 每个添加了scoped的组件渲染出来都会附带一个唯一的属性data-v-xxx;
(2) 一个组件中的所有标签都会带上同样的data-v-xxx属性;
(3) 子组件会带上父组件的data-v-xxx属性;
(4) 在使用第三方的 UI 库时,只会为根元素添加 data-v-xxx 属性,子元素中则不会添加

image.png

2. 到底deep()做了什么?

先说结论::deep() 函数会把属性选择器放在最前面

参考文章:juejin.cn/post/734884…

3. 为什么有时候穿透需要多包裹一层大盒子

案例: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>

现象: 按照上述书写,穿透样式并未生效,仅子组件样式生效

image.png

这是因为并没有生成嵌套关系,父组件中由于添加scoped注册的样式穿透是针对date-v-father这一前置条件下的,也就是:

[data-v-c61e7f05] .second-row {
    color: blue;
}

但是,目前的dom结构下data-v-c61e7f05下面并没有second-row的类名

所以需要调整,父组件使用一个大盒子包裹起来,完成嵌套,即:

// father.vue
<template>
  <div>
    <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>

调整后实际的效果:

image.png

也可以在father组件上仅在son组件上包裹一层大盒子,是一样的原理

<template>
  <div>father</div>
  <div>
    <Son />
  </div>
</template>

4. 为什么去掉scoped样式穿透有时生效,有时不生效

核心遵旨

  1. 去掉scoped样式就变成全局样式了
  2. 添加:deep()是把其属性选择器放在最前面
  3. 第三方UI组件只会为根元素添加 data-v-xxx 属性

4.1 自定义组件穿透案例

// 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>
4.1.1【添加scoped,使用穿透】:穿透样式生效

原因分析:

image.png

4.1.2【移除scoped,使用穿透】:穿透样式未生效

原因分析:由于没有了属性, :deep作用.second-row 没有属性可提,子组件定义样式生效,“穿透无效”

这里并不是说父组件中 :deep(.second-row)没有属性选择器,就不提属性,将.second-row{color:blue}提升为全局,而是此条定义无效

4.1.3【添加scoped,不使用穿透】:子组件样式生效

原因分析:父组件定义的样式带了属性,但优先级没有子组件定义的高【按照选择器的计算是优先级相同的,是因为子组件样式后加载?】 image.png

注意:这里如果子组件移除掉scoped,子组件的优先级降低,就是父组件样式生效

4.1.4【不添加scoped,不使用穿透】:子组件样式生效

原因分析:父组件定义的是全局,但优先级没有子组件定义的高

4.2 UI封装组件穿透案例

案例:

<template>
  <div>
    <div>father</div>
    <el-input class="ipt"></el-input>
  </div>
</template>

<script setup>
</script>

<style scoped>
div{
  color: red;
}
.el-input{
  width: 300px;
}
.ipt .el-input__wrapper {
  background-color: red;
}

</style>
4.2.1【添加scoped,不穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式不生效

原因分析:在scoped中定义是包括data-v-xxx属性选择器的,而UI框架中,只有外层有属性

.ipt .el-input__wrapper[data-v-76ab893a] {
  background-color: red;
}
4.2.2【添加scoped,使用穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式生效

原因分析:使用穿透,可以将属性提前

.ipt[data-v-76ab893a] .el-input__wrapper {
  background-color: red;
}

image.png

4.2.3【移除scoped,使用穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式不生效

原因分析:父组件无scoped,穿透定义无效

4.2.4【移除scoped,不使用穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式生效

原因分析:提升至全局样式,全局生效

5. 样式穿透的一些写法:

写法有:::v-deep>>>:deep()/deep/

具体区分:

如果你使用的是css,没有使用css预处理器,则可以使用>>>/deep/::v-deep

如果你使用的是less或者node-sass,那么可以使用/deep/::v-deep都可以生效。

如果你使用的是dart-sass,那么就不能使用/deep/,而是使用::v-deep才会生效。

但是如果你是使用vue2.7以上版本以及包括vue3::v-deep也会生效,但是会有警告

参考:juejin.cn/post/709057…