写在开头:
Element UI 坑害我不浅,继上次我把element-plus
的版本从 2.2.29
更新至 2.5.3
,被append-to-body
背刺后又一次了!!!!!
- 上次是ElDialog 中
append-to-body
的默认值偷偷的从true
改成了false
,你知道我的心情吗!!!!
这次又是你 ElDialog
!!!!
一、起因
- 最开始,我只是写了一个
ElDialog
,想通过scoped
去改变弹框中的样式
- 这边有个小小的注意点
custom-class
已被 弃用,在 2.4.0 移除, 可直接使用class
<template>
<ElDialog v-model="isShow" class="my-dialog" :show-close="false">
<p>我是快乐小码农</p>
</ElDialog>
</template>
- 可以看出来这个弹框没有设置标题,但是标题的样式占位依旧存在,于是我就是一个小小的心愿,想把弹框中的
el-dialog__header
隐藏掉而已,于是我加入了以下的css代码
<style scoped lang="postcss">
.my-dialog {
:deep(.el-dialog__header) {
display: none;
}
}
</style>
- 是的,你们没有猜错,完全不生效
- 于是我去查看了浏览器生成的代码
- 这边可以查看一下我之前写的另外一篇文章vue3.x:关于深度选择器:deep()的使用 加深一下对
scope
的印象
.my-dialog[data-v-8043d1fc] .el-dialog__header {
display: none;
}
- 这里好像还没有毛病,但是在看一下html代码就发现问题了
<div class="el-overlay" style="z-index: 2002;">
<div role="dialog" aria-modal="true" aria-labelledby="el-id-4984-2" aria-describedby="el-id-4984-3" class="el-overlay-dialog" style="">
<div class="el-dialog my-dialog" tabindex="-1">
<header class="el-dialog__header">
<span role="heading" aria-level="2" class="el-dialog__title"></span>
<!--v-if-->
</header>
<div id="el-id-4984-3" class="el-dialog__body">
<p data-v-8043d1fc>我是快乐小码农</p>
</div>
<!--v-if-->
</div>
</div>
</div>
- 问题出现了,唯一标识 data-v-8043d1fc 的位置错了!!
-
- 在css代码中是属于
.my-dialog
的,但是在html代码中却是跟随着p标签
,这就是导致了我们的样式无法生效的根源了
- 在css代码中是属于
二、Vue 编译生成的 data-v-xxx
1. 在vue的编译过程中,每个组件的模版都会被编译成render函数。而在渲染过程中,Vue会为每个组件的模版生成一个唯一的data-v-xxx属性,用于实现样式隔离和组件之间的隔离
2. 阅资料大概得出以下结论,这边仅提到 scoped
的情况
- 生成data-v-xxx情况: 当在 Vue 单文件组件(.vue 文件)中使用
<style scoped>
标签定义样式时,Vue 会为每个组件的根元素自动生成一个唯一的data-v-
属性,并将这个属性添加到根元素上。 - 不生成data-v-xxx情况: 如果不使用 scoped 样式,即样式不受限于当前组件,那么元素上不会生成
data-v-
属性。
三. 那为什么开头的那种写法不行呢?
经过向同事的请教大概得出以下结论
以下我将我认为影响到了最终编译结果的 Dialog 组件源码
截取出来
<template>
<teleport
:to="appendTo"
:disabled="appendTo !== 'body' ? false : !appendToBody"
>
// ---------------------------------
// 一些其他的标签,这边省略
// --------------------------------
<el-dialog-content
v-if="rendered"
ref="dialogContentRef"
v-bind="$attrs"
:center="center"
:align-center="alignCenter"
:close-icon="closeIcon"
:draggable="draggable"
:overflow="overflow"
:fullscreen="fullscreen"
:show-close="showClose"
:title="title"
:aria-level="headerAriaLevel"
@close="handleClose"
>
//---------------------------------
// 改组件的插槽设置,这边省略
// --------------------------------
</el-dialog-content>
// ---------------------------------
// 一些其他的闭合标签,这边省略
// --------------------------------
</teleport>
</template>
<script lang="ts" setup>
// ---------------------------------
// 一些文件引入,这边省略
// --------------------------------
defineOptions({
name: 'ElDialog',
inheritAttrs: false,
})
// ---------------------------------
// 其他的setup逻辑,这边省略
// --------------------------------
</script>
1. 关于编译后的html上 data-v-xxx
的添加问题
- 按照上面所翻阅的资料来看,添加上
scoped
之后,该Dialog
组件应该有两处受到父组件影响
(1)Dialog组件
的根元素el-overlay
应被父组件的作用域样式影响
(2)插槽
内的元素也认为是父组件所持有并传递进来的(也就是这边的p标签
),也应受到影响
- 但这边呈现出来的结果是,仅
p标签
受到了影响,而根结点el-overlay
没有被父组件的作用域样式影响
(1)这边便要归功于 Vue 内置组件 Teleport 了,这边会发现如果该组件的根元素是 Teleport 标签
的话,那么该组件的根元素便不受父组件影响
(2)因为官方文档中并没有明确说明,所以这边还无法完全肯定是不是只要根元素是 Teleport 标签
的话,就一定不受父组件影响,但目前来看 Dialog 组件
是因为这个原因的
2. 为什么我们在父组件上给 <ElDialog>
标签上添加的class="my-dialog"
,编译后是跟随了el-dialog
这个class,而不是el-overlay
- 这就要提到 Vue 透传 Attributes ,同时也是因为
<el-dialog-content> 标签
上也设置了v-bind="$attrs"
, 所以我们设置的class="my-dialog"
便自动添加到el-dialog-content 组件
的根结点上
3.为什么我们在父组件上给 <ElDialog>
标签上添加的class="my-dialog"
也不受父组件的样式影响
- 这边要注意,scoped 只能影响子组件的根元素,而不深入子组件的的子元素,而
my-dialog
在编译后是位于子组件的子元素了,所以这边无法受到影响
4. css编译出来的结果中,为什么data-v-8043d1fc
是跟随着 my-dialog
- 以下是我猜测结果
-
- 假设忽略掉
Teleport
、Attributes
的影响的话,那么data-v-8043d1fc
确实是属于my-dialog
的,而进行css编译的时候,它并没有考虑到Teleport
、Attributes
的影响,所以导致了最终我们看见的编译结果
- 假设忽略掉
四、测试
- 知道了data-v-xxx的生成大概原理,于是我将style代码改成了以下
<style lang="postcss">
.my-dialog .el-dialog__header {
display: none;
}
</style>
- 去掉了了
scoped
的设置,再去查看浏览器编译后的情况
.my-dialog .el-dialog__header {
display: none;
}
<div class="el-overlay" style="z-index: 2002;">
<div role="dialog" aria-modal="true" aria-labelledby="el-id-4984-2" aria-describedby="el-id-4984-3" class="el-overlay-dialog" style="">
<div class="el-dialog my-dialog" tabindex="-1">
<header class="el-dialog__header">
<span role="heading" aria-level="2" class="el-dialog__title"></span>
<!--v-if-->
</header>
<div id="el-id-4984-3" class="el-dialog__body">
<p>我是快乐小码农</p>
</div>
<!--v-if-->
</div>
</div>
</div>
- 这个时候会发现样式确实生效了,但这样写的话可能就达不到最初想要样式隔离的想法了
- 于是又了最终版的方案
<template>
<div class="my-dialog">
<ElDialog v-model="isShow" :show-close="false">
<p>我是快乐小码农</p>
</ElDialog>
</div>
</template>
<style scoped lang="postcss">
.my-dialog {
:deep(.el-dialog__header) {
display: none;
}
}
</style>
- 这样写得话,编译出来的唯一标识的位置就可以完全正确,样式也可以快乐生效了!