44 el-dialog 的 appendToBody 属性, 导致 vue 响应式失效

前言

我们经常会碰到 一些 模型和视图 不同步的问题

通常意义上 主要的问题为 列表的某响应式数据更新着更新着 后面就变成非响应式对象了, 然后 就造成了 数据一直在更新, 但是 视图的渲染后面就未渲染了, 这是一个由于 模型上的问题 导致的数据的不在响应式更新

又或者 是因为使用了相关 vue 未能够检测到的 api 来更新 对象, 数组 的元素, 然后导致的数据的不同步, 数据更新了 但是视图未更新

然后 我们这里来看一下 另外的一个 由于vue这边视图更新造成的一个数据 不同步的情况, 这里的情况是 模型里面增加了一条数据, 但是 页面上 没有渲染出来

这个问题 还是很有意思, 是和 vue 这边渲染视图元素有一些关系

核心比较关键的元素是 el-dialog, 然后其中有一个 appendToBody 的属性

vue 模型视图不同步同类型题材的文章可以参见

el-tree defaultCheckedKeys配置 和 树上面选中节点不同步问题

特定的操作之后响应式对象不"响应"了(一)

特定的操作之后响应式对象不"响应"了(二)

直接使用 dom api 更新了 #text节点, 之后响应式更新不生效了

测试用例

复制代码
<template>
  <div class="testParent">

    <div v-for="dayPlan in weekPlan" style="display: flex;" >
      <el-tag type="warning">{{dayPlan.name}}</el-tag>
      <div class="block" v-for="biz in dayPlan.children" style="display: inline; float:left;">
        <el-tag type="warning">{{biz.time}}</el-tag>
        <el-input v-model="biz.biz" :name="biz.biz" ></el-input>
      </div>

      <el-dialog :visible.sync="visible" v-if="dialogVisible" width="480px" :show-close="true" :modal="false"
                 :append-to-body="true" >
        <span>是否退出登录?</span>
      </el-dialog>
    </div>

    <el-button style="position: absolute; top: 100px; left : 1500px; " @click="handleClick" > click </el-button>

  </div>

</template>

<script>

  export default {
    name: "HelloElInputUpdate",
    data() {
      return {
        visible: false,
        dialogVisible: true,
        weekPlan: [
          {
            id: "01", name: "monday",
            children: [
              {
                time: "morning",
                biz: "chinese"
              }, {
                time: "afternoon",
                biz: "english"
              }
            ]
          },
          {
            id: "02", name: "tuesday",
            children: [
              {
                time: "morning",
                biz: "math"
              }, {
                time: "afternoon",
                biz: "english"
              }
            ]
          },
          {
            id: "03", name: "wednesday",
            children: [
              {
                time: "morning",
                biz: "math"
              }, {
                time: "afternoon",
                biz: "english"
              }
            ]
          }
        ],
      }
    },
    computed: {},
    mounted() {
      let _this = this
      setTimeout(function() {
        _this.weekPlan[2].children[0].biz = "updated"
        console.log(" updated ")
      }, 5000)

      setTimeout(function() {
        _this.weekPlan[1].children.push({time: "night", biz: "tv"})
        console.log(" newly created ")
      }, 6000)

    },
    created() {
    },
    methods: {
      handleClick() {
        this.visible = !this.visible
      }
    },
  }
</script>

<style>

</style>

正常情况

如果是 不点击 click 按钮来操作 对话框相关

可以看到 mounted 之后的两个操作是正常的, 最终 tuesday 可以看到增加的晚上的计划, wednesday morning 的事项发生了改变

异常情况

此时 就需要我们 在 mounted 的操作之前, 点击 click 按钮了

然后 我们看一下 情况是怎么样的?

可以看到 wednesday morning 的事项发生了改变, 但是 tuesday 增加的晚上的计划 在页面是没有展示出来的

首先我们通过 click 按钮, 来看一下 整个数据模型的情况

可以看到 在数据模型上面, 是正确的, tuesday 增加的晚上的计划 是已经在添加进去了

我们再看一下 页面元素渲染的地方

这里可以看到 vue 这边根据页面元素渲染 vnode 的时候也是正确的, 可以正确拿到 tuesday 的三个计划

这里的 patchVNode 是一层一层的再比较

然后 这里可以通过上下文 parentElm, oldCh, newCh 可以判断出当前是在比较 tuestday 中的改动前后的元素

截图 newCh 总共有五个元素, 一个是 el-tag, 中间三个为 tuesday 的 三个工作计划, 最后一个为 对话框的元素

然后这里 和 oldCh 对比, oldCh 只有四个元素, 一个是 el-tag, 中间两个为 tuesday 的 三个工作计划, 最后一个为 对话框的元素

然后比较 oldCh, newCh 的差异是需要在 oldCh 的第二个工作计划之后再增加一个工作计划 : {time : "night" , biz : "tv"}

然后 后面会 根据 vnode 渲染时间的 dom 元素, 然后 添加到页面中

这里会创建 工作计划 : {time : "night" , biz : "tv"} 的父级 div 元素, 以及子级的 el-tag 和 el-input, 这个具体的细节 我们先暂时不管, 可以理解为我们这里 vnode.elm 即为创建好的 dom 元素

然后 另外一个比较关键的元素是 refElem, 是一个参照元素, 标记了目标新增元素 应该放在那里

在我们这里的场景新增的 工作计划 : {time : "night" , biz : "tv"} 按道理其之后应该是 对话框节点, 所以所这里计算的 refElm 即为对话框对应的节点

但是 这个节点因为我们前面配置了 appendToBody 的配置, 这个节点被移动出去了, 在dom层级上已经没有这个节点了, 甚至连它的 comment 节点也没有了

vue 更新页面元素的时候, 数据的 dom 结构如下

可以看到在 monday, tuesday, wednesday 的数据节点下面是没有只有 el-tag, 两个工作节点, 没有 vnode模型上面的 dialog 节点

然后 dialog 节点是在 外层的 body 下面

然后这里添加 dom 节点的时候, 发现 ref节点 甚至不在 parent 下面, 然后 直接跳过了 dom 元素的添加

因此 最终页面上 看不到这个本应该渲染的 工作节点

引起这里的问题, 主要的原因是 dom 结构变化了, 但是 vnode 的结构却没有发生变化

进而导致 vue 这边根据 vnode 的结构渲染新的元素的时候, 出现异常

解决的方式, 不要将 dialog 放置于可能新增元素的节点 后面, 另外用例中 将 dialog 放置于循环中 本来也是一个问题

如果 appendToBody 配置为 false 会怎么样 ?

点击了 dialog 之后, 可以看到对应的 wrapper 的 div 依然还是在原来的 dom 层级上面

然后 vue 这时候基于 dialog节点 来添加元素, 可以正常添加到

添加目标工作节点的时候

完

相关推荐
可乐鸡翅yeah_3 小时前
FFmpeg 生成 HLS 独立分片 independent‑segments 参数到底有什么用
javascript·ffmpeg·音视频·safari·m3u8
福兮说3 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
Ai-_Man5 小时前
您您这可以把Grok的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑
liangshanbo12155 小时前
JavaScript 异步核心主线
javascript·事件循环·异步操作
传人once6 小时前
悬停旋转放大和位移效果如何写
javascript·css·动画
默_笙8 小时前
🍔 中间件不只是打日志:四个钩子、一次短路,和自带的工具
前端·javascript
Dovis(誓平步青云)9 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
xieter9 小时前
【技术精选】TypeScript 高级类型体操:条件类型与模板字面量深度剖析 (2026-10-06)
javascript
ss2739 小时前
AI全栈实战 | 3.3-02 Python 并发:有 GIL 为什么还用多线程,asyncio 和 JS 事件循环同源不同味
开发语言·javascript·python
CappuccinoRose10 小时前
输入事件进阶
前端·javascript·输入事件