背景
我们做 Vue 项目在组件里写 css 的时候,经常会给 <style>
标签加上 scoped
,比如这样: <style lang="less" scoped>
,这样写出来的 css 就是局部的,不会影响其他组件。
另外,假设我们在自己的组件中引入了一个子组件,并且希望在我的组件中修改子组件中的样式,由于我们用了 scoped
,直接修改是不生效的。去掉 scoped
是可以的,不过没了局部 css
风险不可控。有一种更好的方式,用深度选择器修改,比如 less
中的 /deep/
。用过组件库(如element ui
)的朋友,在覆盖组件库样式的时候应该会经常用到深度选择器 /deep/
。
基于以上两点,我们来聊一聊:
- 为什么
scoped
可以形成局部css
?原理是什么? - 为什么
/deep/
可以跨组件修改css
?原理是什么?
scoped 形成局部 css 原理
先看一张图:

我们审查 html
元素时发现多了很多类似 data-v-f321cf6
属性,这些属性其实就是 scoped
hash
出来的。
再来看一张页面生成的 css
截图:

仔细看第一张图标记了两种颜色,他们分别对应两种 hash
属性值:data-v-f5321cf6
和 data-v-6a6ef68c
,其实这里就是两个不同的组件生成的两个不同的值,每个加过 scoped
的组件生成的值都是唯一的。
结合两张图,不难发现 scoped
形成局部 css
的原理其实很简单,就是先给元素加上 hash
出来的属性,再通过属性选择器来选择这些元素,由于每个组件 hash
出来的属性值都是唯一的,css
属性选择器选出来的元素当然也是组件级的了,因为形成了组件内局部 css
。(如果你还不了解 css
属性选择器,可以看一下这里)
/deep/
跨组件修改 css
原理
一个例子
一个列表页面 ,需要在数据为空的时候展示空白页,这个空白页我们引入的是第三方组件库的空白页组件<no-data>
。

假设我们现在要修改空白页组件中图片的宽高,<no-data>
不支持传入宽高,并且我们无法改动这个第三方组件,只能在列表页通过 css
覆盖空白页组件的样式。
我们第一时间想到改样式嘛,这还不简单,于是写下:
xml
<style lang="less" scoped>
.no-data{
img {
width: 200px;
height: 200px;
}
}
</style>
自信满满的点开浏览器查看效果,发现设置的宽高并没起到作用,空白页图片还是默认的偏大。
仔细一想,父组件用了 scoped
是不能直接改子组件内样式的,得用 /deep/
,于是乎有了下面的代码:
xml
<style lang="less" scoped>
.no-data{
/deep/img {
width: 200px;
height: 200px;
}
}
</style>
再查看效果:

生效了,图片小了很多!
我们再来审查一下页面渲染出来的代码,没加 /deep/
的:

注意看 css
部分,这里的选择器是 .acl-nodata img[data-v-6a6ef68c]
,这里的 data-v-6a6ef68c
属性是 <no-data>
组件中的 scoped
生成的。
接着往下看,加上 /deep/
后的:

还是重点看 css
部分,这里的选择器是 .integral-detail .no-data[data-v-f5321cf6] img
,这里的 data-v-f5321cf6
是列表页的 scoped
生成的。
核心区别在于:img[data-v-6a6ef68c]
和 .no-data[data-v-f5321cf6] img
/deep/
原理
通过上面的例子,我们可以分析出,在使用了 /deep/
选择器后,会把当前元素 img
的 [data-v-6a6ef68c]
干掉,并通过他的父级(准确的说是 img
元素所属空白页组件的根元素 <div class="no-data"></div>
),来查找到 img
,也就是通过 .no-data[data-v-f5321cf6] img
来定位到 img
元素。
因为我们的代码是在列表页写的,列表页 hash
出来的属性是 [data-v-f5321cf6]
,在我们使用 /deep/
之前,img
的 hash
是 [data-v-6a6ef68c]
,在列表页中的 css
代码当然是识别不到空白页组件的 [data-v-6a6ef68c]
,所以修改宽高不生效。使用 /deep/
之后,通过 .no-data[data-v-f5321cf6] img
,由于 [data-v-f5321cf6]
本身就是列表 hash
出来的,自然是可以识别的,修改宽高自然也就生效了。
为了加深理解,这里再提一点,仔细看 <div class="no-data"></div>
这一块的 html
代码,你会发现它是同时具有 [data-v-f5321cf6]
和[data-v-6a6ef68c]
两种属性,因为 <no-data>
组件的根元素是 <div class="no-data"></div>
,在列表页引入 <no-data>
, <div class="no-data"></div>
也相当于是列表中的一个元素,所以 scoped
也会给它 hash
上 [data-v-f5321cf6]
。 (这一块在实际开发中,有些朋友会搞一些骚操作,比如在父组件和子组件同时改 .no-data
)