1. style的scoped属性
scoped属性是为了实现样式隔离,使得组件之间的样式互不影响。
现在我们有
组件A.vue
<template>
<div class="wrapper">
<el-statistic :value="output[0]" prefix="¥" />
<div class="mt-1 title">本月销售总额</div>
</div>
</template>
<!-- js代码省略,用不到 -->
<style scoped lang="scss"></style>
这个组件是一个单根组件,外层有一个div包裹,class=wrapper
,内部使用了element-plus的数据统计组件el-statistic
。
这个组件渲染出的DOM元素如下:
可以发现此时,只有外层容器wrapper上有哈希值:data-v-447f2f22
,实际上这个哈希值是A组件父组件的哈希值,如下:
当前的A组件并没有生成自己的哈希值data-v-xxx,现在只要我们在style内部写入样式代码,即使是注释代码也行,就可以生成A组件自己的哈希值。如下:
<style scoped lang="scss">
//
</style>
这时候A组件生成了自己的哈希值:data-v-d48692a9
(我们暂时不考虑父组件的哈希值)。现在如果我要选中wrapper
容器,设置背景色,就可以有如下代码:
<style scoped lang="scss">
//
.wrapper {
background-color: coral;
}
</style>
实际的选择器为:
就是把A组件
自带的哈希值data-v-d48692a9
,加到了选择器上,使用了类选择器加上属性选择器,选中了wrapper容器。如果选择内部的元素,比如 文本 本月销售总额
,scss代码如下:
<style scoped lang="scss">
//
.wrapper {
background-color: coral;
.title {
color: red;
}
}
</style>
对应的DOM元素为:
实际的css选择器为:
.wrapper .title[data-v-d48692a9] {
color: red;
}
编译机制解析
.wrapper .title
的实际编译结果是.wrapper .title[data-v-d48692a9]
。
在Vue的scoped
样式机制处理后代选择器时,会将data-v-*
作用于选择器的最右侧部分(即目标元素
),而不是每一级都添加。不仅是目标元素,同时还要是当前组件内部的元素
,才会添加这个data-v-*
浏览器在运行时会从右到左解析选择器。
结合scoped编译机制
和浏览器匹配机制
,就可以理解为为什么可以选中对应的元素。
如果最后的目标元素,不是当前组件内部的元素,比如是子组件内部的DOM元素,则 Vue 不会为该元素附加当前组件的 data-v-* 标识。当然,样式匹配不到目标元素。
2. :deep()
:deep()
的作用是让特定的样式可以穿透到子组件的 DOM 中,作用于子组件的某些元素。
比如我们希望在父组件中修改子组件的样式。
示例代码:
子组件test.vue:
<template>
<div class="test">这里是test组件,是子组件</div>
</template>
<style scoped></style>
在父组件A中使用这个组件:
父组件A.vue:
<template>
<div class="wrapper">
<el-statistic :value="output[0]" prefix="¥" />
<div class="mt-1 title">本月销售总额</div>
<!-- 引入子组件 -->
<test></test>
</div>
</template>
现在我在A组件的样式文件中,选择test
组件中根元素div class="test"
,是可以选中的。
<style scoped lang="scss">
.wrapper {
background-color: coral;
.title {
color: red;
}
}
//test子组件
.test {
color: blue;
}
</style>
能选中的原因是因为,test
组件的根元素上存在着父组件A
的data-v-d48692a9
哈希值:
根据scoped样式机制,针对选择器.test
,他选中的元素是在当前组件A.vue
中的,同时也是目标元素,那么就给这个选择器加上了哈希值data-v-d48692a9
,变成.test[data-v-d48692a9]
,这样正好可以选中。
如果我们的test组件中还有其它嵌套的DOM,我们还是这样选择,可以选中吗?
test.vue
<template>
<div class="test">
这里是test组件,是子组件
<div class="test-inner">test内部</div>
</div>
</template>
//A组件的style
<style scoped lang="scss">
.wrapper {
background-color: coral;
.title {
color: red;
}
}
.test {
color: blue;
.test-inner {
font-size: 30px;
color: green;
}
}
</style>
这样是不能生效的,如图。
原因是因为:在这种父子嵌套的结构中,子组件是一个单根组件,那么这个子组件的根元素上就会带着父组件的哈希值,但是其内部的DOM并不会带上,如图:
这也就意味着,子组件的根元素是在渲染父组件中的,所以有父组件的哈希值,但是子组件内部的dom元素,是属于子组件
那么我们写的这个选择器,理论上是.test .test-inner[data-v-d48692a9]
,虽然.test-inner
是目标元素,但他不是A.vue
组件内部的元素,他没有data-v-d48692a9
属性,vue也不会把data-v-d48692a9
附加到.test-inner
上。
这时候就轮到了:deep()
登场了。
如果我们把A
组件中的样式代码改为:
.test {
color: blue;
:deep(.test-inner) {
font-size: 30px;
color: green;
}
}
效果如图
我们查看css选择器如下:
根据GPT的回答,和我自己的总结,
加上:deep()
以后,用来表示当前样式应该跳过 scoped 的作用域限制,因此编译后其选择器部分不会附加 data-v-*
,意味着不要编译为.test-inner[data-v-*]
的形式,而是保留为.test-inner
的形式。
然后vue的scoped样式机制是针对你的选择器,从左向右的添加data-v-*
的,从左向右开始找,找到你的目标元素,同时也是当前组件的内部元素,就会添加这个data-v-*
,因为这针对目标元素,所有只会有一个元素被添加这个哈希值。
比如.test :deep(.test-inner)
,deep的作用是告诉vue不给.test-inner
添加哈希值,但是他是目标元素啊,不给他加就会给他的最近的祖先(猜的),同时还是当前组件内部的元素添加,这里就是.test
元素,也就有了上面的编译结果。
那如果没有祖先呢,比如:
:deep(.test) {
color: red;
}
实际编译后的css为[data-v-d48692a9] .test
首先.test
是A组件内部的元素,也是目标元素,但是加上deep
则是告诉vue不要给他加哈希值,这个选择器又没有父级,这时就会直接添加[data-v-d48692a9]
相当于选中所有的带有这个属性的元素。
deep的用途就是用来在父组件中修改子组件内部元素的样式,所以通常是用来修改组件库内部的样式的,针对我们自己的组件,直接在组件中修改样式就行了,基本用不到deep。
3. 使用el-statistic组件,并修改样式
组件代码:
<template>
<div class="wrapper">
<el-statistic :value="output[0]" prefix="¥" />
<div class="mt-1 title">本月销售总额</div>
</div>
</template>
<style scoped lang="scss">
.wrapper {
background-color: lightgrey;
}
</style>
el-statistic
现在就是一个组件,作为当前组件的子组件,被引入进来,现在我们的需求是将目前的样式:
修改为下面的样式:
假如直接添加类class="statistic"
,然后添加样式,实际是不生效的,如下:
<el-statistic :value="output[0]" prefix="¥" class="statistic" />
<style scoped lang="scss">
.statistic {
color: #fff;
}
</style>
查看元素:
可以看到¥
符号和数字74811
并不是statistic
元素的直接文本,有其它盒子包裹了这个文本。当前的样式选中的元素是class=el-statistic statistic
的div
。实际上我们需要选中的是这个子组件内部的元素:el-statistic__prefix
内部的span
元素,所有首先我们需要选中el-statistic__prefix
元素,这个逻辑正好就是在父组件中选中子组件内部的元素,所以这里使用:deep()
可以生效。
.el-statistic {
color: green;
:deep(.el-statistic__prefix) {
color: blue;
}
:deep(.el-statistic__number) {
color: red;
}
}