在 Vue
项目的开发中,很多人都因为想要限制 CSS
样式的作用范围(避免样式污染的问题)去使用 scope
属性。在实际情况中scope
又会限制文件样式的修改。
有很多同学是使用的 Scss
以及 Less
的,对于他们来说什么时候使用 /deep/
什么时候使用 ::v-deep
是很困扰的。今天咱们来讲讲/deep/
、::v-deep
、>>>在什么情况下使用。
Scoped CSS的原理
先带大家看一下无设置Scoped
与设置Scoped
的区别在哪
无设置Scoped
<div class="login">登录</div>
<style>
.login {
width: 100px;
height: 100px
}
</style>
打包之后的结果是跟我们的代码一摸一样的,没有区别。
设置Scoped
<div class="login">登录</div>
<style scoped>
.login {
width: 100px;
height: 100px
}
</style>
打包之后的结果是跟我们的代码就有所区别了。如下:
<div data-v-257dda99b class="login">登录</div>
<style scoped>
.login[data-v-257dda99b] {
width: 100px;
height: 100px
}
</style>
我们通过上面的例子,不难发现多了一个data-v-hash属性,也就是说加了scoped,PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom,可以使得组件之间的样式不互相污染。
讲完这些,咱们来看看样式穿透问题。
深度作用选择器原理
如果你希望 scoped 样式中的一个选择器能够选择到子组 或 后代组件中的元素,我们可以使用 深度作用选择器
,它有三种写法:
-
>>>
,示例: .gby div >>> #dyx p
-
/deep/
,示例: .gby div /deep/ #dyx p
或 .gby div/deep/ #dyx p
-
::v-deep
,示例: .gby div::v-deep #dyx p
或 .gby div::v-deep #dyx p
它的原理与 Scoped CSS 的原理基本一样,只是第3步有些不同(前2步一样),具体如下:
- 为每个组件实例(注意:是组件的实例,不是组件类)生成一个能唯一标识组件的标识符,我称它为实例标识,记作 InstanceID;
- 给组件模板中的每一个标签对应的Dom元素(组件标签对应的Dom元素是该组件的根元素)添加一个标签属性,格式为
data-v-实例标识
,示例:<div data-v-e0f690c0="" >
; - 给组件的作用域样式
<style scoped>
的每一个深度作用选择器前面的一个选择器单元增加一个属性选择器[data-v-实例标识]
,示例:假设原选择器为 .cls #id >>> div
,则更改后的选择器为 .cls #id[data-v-e0f690c0] div
;
因为Vue不会为深度作用选择器后面的选择器单元增加 属性选择器[data-v-实例标识]
,所以,后面的选择器单元能够选择到子组件及后代组件中的元素;
在 Vue2
版本中:
- 如果你是使用的
Stylus
以及 CSS
那么不用考虑直接使用 CSS
所支持的 >>>
来穿透就可以了。 - 如果说你使用的是
Less
以及 Sass
的话,那么推荐使用 ::v-deep
来实现样式穿透的效果。
为什么不使用 /deep
呢,因为现在的 Sass
默认安装的是 dart-sass
如果你使用 /deep/
会提示错误:SassError: expected selector. /deep/
,所以直接使用 ::v-deep
预防可能会出现的问题。
并且最好不要直接使用 ::v-deep
等样式穿透来书写,比如说:
<style scoped lang="scss">
::v-deep .className {
...
}
</style>
这样的话,其实就和你没有添加 scope
的结果是一样的了,并不会只限制在当前组件内。而会污染到全局样式当中。
并且直接使用 /deep/ .className
可能无法通过 loader
的编译。
如果说你使用的是 Vue3
的话,就需要把 ::v-deep
替换成 :deep()
了
一般都会提供一个 custom-class
给你使用,可以借助这个来起一个 className
来给它们添加样式,或者可以直接使用 class
来给当前页面中的弹窗元素起一个CSS类名。
然后写在全局就可以了,没有必要通过样式穿穿透来覆写。当然也可以写在 scope
内,因为第一级会被添加上当前组件的 hash
值。来起到限制作用域的要求。