文章目录
scoped
在vue组件中<style>
标签中使用scoped属性可以实现各个组件的样式隔离。当组件中使用scoped
就表示该组件的样式只供该组件使用。
案例前提:
有两个组件是父子关系,现在研究两个组件使用scoped和不使用scoped的情况:
父组件 (父组件中引入子组件)
<template>
<div class="parent">
<h1>parent组件</h1>
</div>
<ChildrenComponents />
</template>
<script setup>
//引入组件
import ChildrenComponents from "./components/ChildrenComponents.vue";
</script>
<style></style>
子组件
<template>
<div>children组件</div>
</template>
<script setup></script>
<style></style>
当不使用scoped属性时
当不使用scoped属性时,组件定义的样式是全局
的样式。
且父组件中定义的样式的优先级高于子组件样式的优先级,当父子组件都定义了同一个标签的样式,那么标签样式以父组件中为准
- 父组件定义样式,子组件不定义样式:
父组件:
<template>
<div class="parent">
<h1>parent组件</h1>
</div>
<ChildrenComponents />
</template>
<script setup>
//引入组件
import ChildrenComponents from "./components/ChildrenComponents.vue";
</script>
<style>
div {
width: 200px;
height: 200px;
background: red;
border: 5px black solid;
}
</style>
子组件:
<template>
<div>children组件</div>
</template>
<script setup></script>
<style></style>
样式:
即父子组件都引用父组件中定义的div样式。
- 子组件定义样式,父组件不定义样式:
父组件:
<template>
<div class="parent">
<h1>parent组件</h1>
</div>
<ChildrenComponents />
</template>
<script setup>
//引入组件
import ChildrenComponents from "./components/ChildrenComponents.vue";
</script>
<style>
</style>
子组件:
<template>
<div>children组件</div>
</template>
<script setup></script>
<style>
div{
width: 100px;
height: 100px;
background: orange;
border: black 5px solid;
}
</style>
即父子组件都引用子组件中定义的div样式。
- 父组件定义样式,子组件也定义样式:
父组件
<template>
<div class="parent">
<h1>parent组件</h1>
</div>
<ChildrenComponents />
</template>
<script setup>
//引入组件
import ChildrenComponents from "./components/ChildrenComponents.vue";
</script>
<style>
div {
width: 200px;
height: 200px;
background: red;
border: black 5px solid;
}
</style>
子组件
<template>
<div>children组件</div>
</template>
<script setup></script>
<style>
div {
width: 100px;
height: 100px;
background: orange;
border: black 5px solid;
}
</style>
以父组件样式为准,即父组件样式优先级高于子组件
使用scoped属性时
当父子组件都是用scoped时,父子组件分别使用自己的样式互不冲突。(scoped中定义的样式就是自己的)
- 父子都使用scoped
父组件
<template>
<!-- <div></div> -->
<div>parent组件</div>
<ChildrenComponents />
</template>
<script setup>
//引入组件
import ChildrenComponents from "./ChildrenComponents.vue";
</script>
<style scoped>
div {
width: 200px;
height: 200px;
background: red;
border: black 5px solid;
}
</style>
子组件
<template>
<br />
<div>children组件</div>
</template>
<script setup></script>
<style scoped>
div {
width: 100px;
height: 100px;
background: orange;
border: black 5px solid;
}
</style>
使用scoped
隔离样式,两个组件分别使用自己的组件,scoped
定义的样式只能在自己的组件中使用。
- 父组件不使用,子组件使用scoped
子组件使用自己的scrop
样式,父组件使用父组件定义的全局样式 - 父组件使用scoped,子组件不使用
父组件使用自己的scrop
样式,子组件使用子组件定义的全局样式。
预留一个问题
遇到一个问题,上面的scope
案例中,子组件vue组件的模板中,所添加的样式标签前必须 有一个标签
,或者其被一个标签包裹
,scoped
样式才能生效,这个问题下面说。
scoped样式渲染小结
- 都不设置scoped时,组件中定义的样式就是全局样式,其他所有组件都可以使用,并且父组件的样式优先级高于子组件的样式优先级
- 设置scoped时,scoped定义的样式只对该组件有效。如果该组件中使用的的样式在该组件的
scoped
中没有设置的时候就去全局中寻找样式。 - 使用
scope
也是遵循样式渲染的父子级关系的,即样式继承
scoped演示渲染的原理
在当前组件的.vue文件中,如果style标签加了scoped属性,那么在组件渲染为DOM时,会对每个组件中的DOM元素添加格式为:data-v-[hash:8]
的属性
,然后该组件的所有选择器
也会添加上对应的[data-v-[hash:8]]
属性选择器, 通过这种方式来只对自身组件产生影响,实现样式隔离。
scoped样式的单双节点问题
上面有一个预留问题,样式标签 前面必须有一个标签
或者样式标签被一个标签包围
scoped样式才能生效。
问题描述(前提是父子组件都使用了scoped
)
如果子组件样式标签不做任何处理就会样式就会根据父组件显示:
子组件:
<template>
<!-- <br /> -->
<div class="text">children组件</div>
</template>
<script setup></script>
<style scoped>
div {
width: 100px;
height: 100px;
background: orange;
border: black 5px solid;
}
</style>
父组件
<template>
<div>parent组件</div>
<ChildrenComponents />
</template>
<script setup>
//引入组件
import ChildrenComponents from "./ChildrenComponents.vue";
</script>
<style scoped>
div {
width: 200px;
height: 200px;
background: red;
border: black 5px solid;
}
</style>
子组件显示为父组件样式
发现子组件拥有父组件和子组件的scope
定义的属性。
如果子组件样式标签前添加一个标签
子组件:
<template>
<br />
<div class="text">children组件</div>
</template>
<script setup></script>
<style scoped>
div {
width: 100px;
height: 100px;
background: orange;
border: black 5px solid;
}
</style>
子组件的标签上只有子组件对应的scope
属性
如果子组件样式标签被一个标签包裹
<template>
<div>
<div class="text">children组件</div>
</div>
</template>
<script setup></script>
<style scoped>
div {
width: 100px;
height: 100px;
background: orange;
border: black 5px solid;
}
</style>
子组件的包裹标签上同时有父组件和子组件对应的scope
属性。
问题原因
通过上面的例子的标签样式我们可以知道:
- 如果子组件只有一个标签,父组件的样式就可以影响到子组件
第一层的标签
的样式 - 如果子组件有多个标签,父组件的样式就不会影响到子组件
这是因为:
-
子组件中只有一个标签的时候:
当代码进行解析,子组件外层的template
不会被解析到代码中,子组件单标签就会被直接解析到父组件的模板代码中,子组件的单标签就会被解析成父组件中一个标签元素,当解析 父组件的scope样式的时候 ,就会给父组件的所有标签都添加上scope对应的[data-v-[hash:8]]
属性,子组件的单标签的最外层上
也会添加相应的属性;当解析 子组件的scope样式的时候 ,子组件的所有标签都会被添加上子组件scope
对应的[data-v-[hash:8]]
属性,最外层标签当然也会添加上。此时子组件的最外层标签上就既有子组件对应的scoped对应的属性,又有父组件对应的scoped属性,即父子组件的scoped对应的样式都会被渲染到。但是由于父组件样式的优先级高于子组件
,所以渲染成父组件的样式, -
子组件中有多个标签的时候:
vue3解析的时候会将模板代码中多个标签包含在一个Fragment虚拟元素
中,即子组件的标签外面相当于嵌套了一个fragment
标签, 所以解析的时候父组件的scope
样式对应的属性会添加到fragment
虚拟标签上,但是fragment
只是一个虚拟标签最后不会被解析成真实的dom节点,所以最终看到的效果是子组件标签上都是子组件的scoped
对应的属性。所以最终的样式是子组件渲染的是子组件的样式,父组件渲染的是父组件的样式。
问题总结
所以使用scoped
的时候需要注意:(前提是父子组件都使用了scoped
)
- 如果子组件是单标签,那么子组件最外层的样式会跟着父组件的样式走,内层的样式跟着子组件走
- 如果子组件是多标签,那么子组件的标签样式都跟着子组件走
总结:
- 开发的时候如果父子组件都使用
scoped
样式,子组件最好使用多标签,实现真正的样式隔离
样式穿透
样式穿透的使用场景
当我们在vue项目中引用到第三方组件时,比如element-ui,常常需要更改某些元素的样式,但如果使用常规的css样式覆盖的方式是经常无效的。
无效的原因通常有两个
- 样式优先级不够
当第三方组件没有使用scoped
时,通常是因为在自己代码中设置的样式优先级没有第三方组件中设置的样式优先级高,这个时候我们一般添加!important
来进行解决。 - 第三方组件使用scoped
通过上面的结论我们知道,如果第三方组件的样式设置了scoped
属性,那么第三组件会优先读取scoped
设置的样式属性,如果scoped
中没有才回去获取全局样式。问题是我们修改第三方组件的样式的时一般都是抓取的第三方组件中scoped
设置的样式类名,且我们直接设置样式是不会有属性选择器的,那么设置样式自然是无效的。这种情况就需要使用样式穿透
来进行样式修改了。
( 根据上面的scoped单双组件研究
有一种情况:如果第三方组件是单组件,并且最外层就直接设置了样式,那么在父组件的scoped中设置同名样式是有作用的。
但是为了简便为第三方组件设置样式的时候我们一般就直接使用下面的通用的样式穿透来设置样式了。)
样式穿透的使用方法
使用样式穿透的前提是style标签中需要添加scoped
属性
普通的css语法
- Vue2中的样式穿透:
.类名 >>> .类名{样式}
- Vue3中的样式穿透:
// 第一种
:deep(.类名){样式}
// 第二种
::v-deep(.类名){样式}
scss语法
在要修改的样式前添加 ::v-deep
::v-deep .el-card__header{
border:none;
}
sass语法/less语法
在要修改的样式前添加::v-deep
或 /deep/
.wrap /deep/ .el-card__header{
border:none;
}
::v-deep适应性更好,更多地使用
样式穿透的原理
所谓样式穿透,就是在父级组件
中强制去修改子级组件的内部样式
,注意这里的父子层次并不一定是一级,可能是很多级。
原理是:
假设父元素的scoped对应的属性是data-v-70000000
,第三方组件子元素的scoped对应的属性是data-v-80000000
,在父元素(带有.parent类)中需要设置子元素.son{}
样式。
- 当不设置样式穿透时,父组件给子组件的son设置的样式是这样的:
[data-v-70000000] .parent{
样式......
}
// 没有给son设置样式
同时子组件的样式:
[data-v-80000000] .son{
样式......
}
不设置样式穿透的时候父组件不会对子组件中的样式类设置样式,自然就会直接取值子组件自己设置的样式
- 当设置样式穿透时,父组件给子组件的son设置的样式是这样的:
[data-v-70000000] .parent{
样式......
}
// 给son设置样式
[data-v-70000000] .son{
样式......
}
同时子组件给自己设置的样式:
[data-v-80000000] .son{
样式......
}
父组件设置的样式[data-v-70000000] .son
可以对应到子元素的标签,同时父组件样式的优先级高于子组件样式的优先级,所以设置的样式会生效。
注意
- 设置样式穿透的前提是,父组件的
<style>
标签设置scoped
属性 - 必须是父元素使用样式穿透,之前使用祖先元素设置样式穿透竟然不起作用