导言
在vue的开发中我们常常用到第三方的组件库来协助我们开发,但有时候默认样式并不是我们想要的,所以常常需要覆盖原来组件的样式。然而样式覆盖的时候就会发现一个问题,直接在 <style scoped> 中进行组件覆盖时,有些组件可以直接生效,而有些组件必须要样式穿透才生效(利用::deep() /deep/ ::v-deep等深度选择器),这到底是为什么呢?
实际开发中遇到的疑惑
我在 scoped 的样式表里将表格的边框改成 1px 的实体黑色边框, 并且将表头的每个单元格的背景色设置为红色。
实际效果如下图,只有 el-table的边框更改生效了,el-table__cell 的背景色更改不生效
要使它生效只有两个方法
第一种:使用 ::deep 样式穿透
第二种: 不在scoped中使用,且要使用 !important 强制覆盖(不使用 !important 权重不够,原组件库的样式权重更高),但第二种方法会导致全局样式污染,所有表格的表头都被改了
生效图如下
为什么 el-table可以直接生效,而el-table__cell 要样式穿透才能生效呢?要解决这个疑惑,先得从vue的scoped原理以及深度选择器的原理出发
style scoped 的原理
基本原理:为每个组件实例生成一个唯一表示组件实例的标识符,从而实现样式隔离
基本原理大家都知道,接下来要讲解scoped 应用的细节才是理解问题的关键
- 为每个组件实例生成一个唯一能表示组件实例的标识符,建成实例标识
- 给组件模版中的每一个标签对应的Dom元素(组件标签对应的Dom元素是该组件的根元素)添加一个标签属性,格式为data-v-实例标识,示例:<div data-v-e0f690c0="" >;
- 给组件的作用域样式 <style scoped> 的每一个选择器的最后一个选择器单元增加一个属性选择器 原选择器[data-v-实例标识] ,示例:假设原选择器为 .cls #id > div,则更改后的选择器为 .cls #id > div[data-v-e0f690c0];
官网上说:使用 scoped 后,父组件的样式将不会渗透到子组件中。不过,子组件的根节点会同时被父组件的作用域样式和子组件的作用域样式影响。 这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。cn.vuejs.org/api/sfc-css…
即:组件内部,包含子组件的根标签,但不包含子组件的除根标签之外的其它标签;所以 组件的css选择器也不能选择到子组件及后代组件的中的元素(子组件的根元素除外);
还是继续上面应用的场景,我们打开控制台看到,el-table 作为子组件的根节点在页面组件中使用被加上了标签属性,所以匹配上了 <style scoped> 里转化后的样式——被添加了属性选择器的 .el-table[data-v-ac1778d4]
而我们查看.el-table__header .el-table__cell 会选中的元素,其元素上并没有 data-v-xxx 属性(原因就是上面标黄的那段字),所以和 里转化后的样式 .el-table__header .el-table__cell[v-xxxx] 匹配不上,所以样式就不生效喽
为什么使用深度选择器就生效了呢?我们得了解它的原理
>>>、/deep/、::v-deep深度选择器的原理
它的原理与 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;
继续上面应用的场景,我们使用::deep,发现样式生效了,使用deep 后,他给使用了深度选择器的 .el-table__header .el-table__cell 添加了当前组件的hash值,就能匹配上了
其他能直接生效的样式覆盖
试了一下 el-form-item 直接在 中进行组件覆盖时可以直接生效了,这是为啥呢?
因为 el-form-item 是父组件插槽中的内容,插槽中的内容也视为父组件的元素,所以dom 元素也被加上了data-v-xxx 的属性选择器,就能和转化后的样式——被添加了属性选择器的 .el-form-item[data-v-11a15869] 匹配上了
总结
- 能直接在scoped里改的元素:一种是根节点,需要子组件把被调用时给的class作为根节点的class,而根节点视为父组件的元素,会被加上加上data-v;另一种情况,说明改的是组件插槽中的内容,插槽中的内容也视为父组件的元素。
- 不能直接生效的元素就用深度选择器就可以样式覆盖了。
附录
vue 关于 scoped 的源码(可以去看看源码咋实现的
参考文档