学习Vue里的scoped CSS

1,313 阅读2分钟

最近在学习使用Vue的时候,发现定义在<style scoped>里的样式有部分没有生效,于是就去查询资料学习,并记录下学习的内容。我刚入行不久,如果有什么不对的地方欢迎指正~

结论

造成定义的样式不生效的主要原因就是这个scoped属性。 在使用了scoped属性后,给当前组件的子组件创建的样式就会不生效,包括一些第三方的组件库(我所使用的是element-ui)。

为什么使用了scoped属性后,给子组件的样式不生效?

<style>标签拥有了scoped属性后,它的CSS只会应用于当前这个组件中,这和Shadow DOM样式封装很相似。它使用的时候会有一些警告,但不需要任何的polyfill。这是使用PostCSS实现的:

<template>
    <div class="example">hi</div>
</template>

<style scoped>
.example {
    color:red;
}
</style>

转换为如下:

<style>
.example[data-v-f3f3eg9] {
  color: red;
}
</style>

<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>

我们可以看到html和css都加上了data attribute,所以就算其他组件有.example这个class,但没有对应的data attribute的话,样式也不会生效,所以当前这个class的样式也不会污染其他元素的样式。

也正因为如此,当我们调用子组件的时候,子组件的内部元素也无法获取到外部设置的css样式,例子如下:

最终效果: Snipaste_2022-01-23_03-25-16.png

源代码:

<!--父组件(HelloWorld.vue) -->
<template>
  <div class="hello">
    <h1>Hello,World!</h1>
    
    <hi-world />
  </div>
</template>

<script>
import HiWorld from "./HiWorld.vue";

export default {
  name: 'HelloWorld',
  components:{
    HiWorld
  }
}
</script>

<style scoped>
.hello h1{
  color:red;
}
</style>

<!-- 子组件(HiWorld.vue)-->
<template>
  <div>
    <h1>Hi,World!</h1>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>

转换后的代码如下:

<div class="hello" data-v-469af010="">
    <h1 data-v-469af010="">Hello,World!</h1>
    
    <div data-v-469af010="">
        <h1>Hi,World!</h1>
    </div>
</div>

可以看到父组件HelloWorld.vue中的元素都带上了data attribute属性,但对于子组件HiWorld.vue的内部元素,则没有这个属性。所以定义在父元素中的样式并不会影响到子组件中的元素。

解决办法

Vue2

Vue2的写法在Vue3中也会生效,但这种写法在Vue3中已经被弃用。

1. 使用Sass等预处理器时:::v-deep

::v-deep .child-class {
    background-color: #000;
}

2. 不使用预处理器时:>>>

>>> .child-class {
    background-color: #000;
}

Vue3

在Vue3中,前缀::v-现在已经被弃用了,而且我们不论是否使用了如Sass等预处理器,都可以只使用统一的:deep选择器,不过现在推荐使用带括号的选择器。

:deep(.child-class) {
    background-color: #000;
}

除此之外

子组件的根节点

当使用scoped时,父组件的样式不会泄露到子组件中。但是,一个子组件的根节点还是会被父组件的scoped CSS和子组件的scoped CSS影响。这是设计使然所以父组件可以出于布局的原因去改变子组件根节点的样式。下面是例子:

最终效果:

example.png

源代码:

<!-- 父组件HelloWorld.vue,比较上面代码只修改了样式的部分 -->
<template>
  <div class="hello">
    <h1>Hello,World!</h1>
    
    <hi-world />
  </div>
</template>

<script>
import HiWorld from "./HiWorld.vue";

export default {
  name: 'HelloWorld',
  components:{
    HiWorld
  }
}
</script>

<style scoped>
.hello h1{
  color:red;
}

.hello div {
  width: 20%;
  border: 5px solid;
  margin: auto;
}
</style>


<!-- 子组件HiWorld.vue,比较上面代码无变化 -->
<template>
  <div>
    <h1>Hi,World!</h1>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>  

我们可以看到子组件的根节点<div>元素受到了父组件scoped CSS的影响。这点也可以从编译后的代码看出:

<div class="hello" data-v-469af010="">
    <h1 data-v-469af010="">Hello,World!</h1>
    
    <div data-v-469af010="">
        <h1>Hi,World!</h1>
    </div>
</div>

scoped CSS并没有消除class、id选择器的需求

由于浏览器渲染CSS选择器的方式,p { color: red }在scoped CSS(结合了attribute选择器)中会相对来说慢很多。相对的,如果你使用class或者id选择器,比如.example { color:red },那你就计划消除了性能影响(performance hit)。

slotted选择器

默认情况下,scoped的样式是不会影响到<slot/>里的内容的,因为他们被父组件(传递内容进去的组件)所拥有。使用:slotted伪类可以显式的命中slot中的内容。简单例子如下:

最终效果:

example.png

源代码:

<!-- 父组件HelloWorld.vue -->
<template>
  <div class="hello">
    <h1>Hello,World!</h1>
    
    <!-- 使用了<slot/>,并传入内容 -->
    <hi-world>
      <h1>Hi,World!</h1>
    </hi-world>
  </div>
</template>

<script>
import HiWorld from "./HiWorld.vue";

export default {
  name: 'HelloWorld',
  components:{
    HiWorld
  }
}
</script>

<style scoped>

</style>

<!-- 子组件HiWorld.vue -->
<template>
  <div>
    <slot/>
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>
:slotted(h1) {
  color: red;
}
</style>  

参考资料

  1. vue-loader.vuejs.org/guide/scope…
  2. medium.com/@debbyji/de…
  3. v3.vuejs.org/api/sfc-sty…
  4. stackoverflow.com/questions/4…