scoped原理小记

578 阅读2分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

前言

scoped也叫范围样式,曾经是h5的新特性,只是后面又取消了, 具体可以参考这篇帖子github.com/whatwg/html… , 后面vue受到启示,借用了这个概念,又重新实现了一次。
在Vue中当一个style标签拥有scoped属性时候,它的css样式只能用于当前的Vue组件,可以使组件的样式不相互污染。如果一个项目的所有style标签都加上了scoped属性,相当于实现了样式的模块化。

scoped原理

Vue中的scoped属性的效果主要是通过PostCss实现的。以下是转译前的代码:

<template>
   <div class="main">
      <p class="title">title</p>
   </div >
</template>


<style scoped>
   .title{
      margin: 0 0 10px 0;
   }

</style>

转换结果:

<div data-v-f3d719be class="main">
    <p data-v-f3d719be class="title">
        test
    </p>
</div>
<style> 
.title[data-v-f3d719be] {
    margin: 0 0 10px 0;
}
</style> 

既:PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性,给css选择器额外添加一个对应的属性选择器,来选择组件中的dom,这种做法使得样式只作用于含有该属性的dom元素(组件内部的dom)。

scoped带来的问题

test.vue文件

<template>
   <div class="main">
      <p class="title">title</p>
      <HelloWorld></HelloWorld>
   </div >
</template>

<style lang="less" scoped>
   .title{
      margin: 0 0 10px 0;
      .content{
         .hello{
            color:green;
         }
      }
   }

</style>

helloworld.vue文件

<template>
  <div class="content">
    <p class="hello">
      helloworld
    </p>
  </div>
</template>

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

<style lang="less" scoped>
 .hello{
  margin: 40px 0 0;
   /*color:red;*/
}

</style>

渲染后的结果

WeChat083c1812c622b968031ec86a5d00e8e8.png 可以看到红框里的div 有两个data-v-hash, 一个是父组件的scoped生成的,一个是子组件scoped生成的, 所以父组件生成的data-v-hash仅仅是在子组件的最外层的div才会有,而在子组件的内部元素是不存在父组件生成的data-v-hash。
假设现在有一个这样的需求,需要在父组件里面控制子组件的样式,从而达到不影响别的使用了该组件的页面,但是scoped关键字使得,父组件的样式仅仅作用于父组件的元素和子组件的最外层div,假设子组件的代码如下:

<template>
  <div class="son">
    son
    <p class="grandson">
      helloworld
    </p>
  </div>
</template>

父组件的代码为下:

<template>
   <div class="main">
      <p class="title">title</p>
      <HelloWorld></HelloWorld>
   </div >
</template>

<style lang="less" scoped>
   .title{
      margin: 0 0 10px 0;
   }

   .main .son{
      color:red;
   }

   .main  .son .grandson{
      color:yellow !important;
   }

</style>

效果如下:

image.png
可以看到.main .son .grandson{ color:yellow !important; }即使加了!important,也没有生效。

解决方案

加入/deep/即可生效:

.main /deep/ .son .grandson{
   color:yellow ;
}

效果如下:

image.png 可以看到已经生效。 在css中/deep/应该改为>>>, 在vue3中应该改为::v-deep。 当然还有其他方案,就不再赘述。

总结

看到一篇文章写到scoped是h5的新特性, 心中非常疑惑,印象中scoped是vue的特性,怎么成了h5的特性了,于是多方求证, 原来h5确实曾经规划过范围样式scoped,只是大部分浏览器都没有实现,后面又取消了这个特性, 而vue借鉴了这个概念,并且自己实现了该特性。