又是被Element UI 坑害的一天

228 阅读4分钟

写在开头:

Element UI 坑害我不浅,继上次我把element-plus 的版本从 2.2.29 更新至 2.5.3,被append-to-body 背刺后又一次了!!!!!
  • 上次是ElDialog 中 append-to-body 的默认值偷偷的从 true 改成了 false,你知道我的心情吗!!!!
这次又是你 ElDialog !!!!

一、起因

  1. 最开始,我只是写了一个ElDialog,想通过 scoped 去改变弹框中的样式
  • 这边有个小小的注意点 custom-class 已被 弃用,在 2.4.0 移除, 可直接使用 class
<template>
    <ElDialog v-model="isShow" class="my-dialog" :show-close="false">
        <p>我是快乐小码农</p>
    </ElDialog>
</template>
  1. 可以看出来这个弹框没有设置标题,但是标题的样式占位依旧存在,于是我就是一个小小的心愿,想把弹框中的el-dialog__header隐藏掉而已,于是我加入了以下的css代码
<style scoped lang="postcss">
    .my-dialog {
        :deep(.el-dialog__header) {
           display: none;
        }
    }
</style>
  • 是的,你们没有猜错,完全不生效
  1. 于是我去查看了浏览器生成的代码
    .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标签,这就是导致了我们的样式无法生效的根源了

二、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

  • 以下是我猜测结果
    • 假设忽略掉 TeleportAttributes 的影响的话,那么 data-v-8043d1fc 确实是属于 my-dialog 的,而进行css编译的时候,它并没有考虑到 TeleportAttributes 的影响,所以导致了最终我们看见的编译结果

四、测试

  1. 知道了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>
  • 这个时候会发现样式确实生效了,但这样写的话可能就达不到最初想要样式隔离的想法了
  1. 于是又了最终版的方案
<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>
  • 这样写得话,编译出来的唯一标识的位置就可以完全正确,样式也可以快乐生效了!

又是学习新知识的一天!