Vue3 报错:Cannot read properties of null (reading 'insertBefore')

一不小心又来了一个线上问题。搜了网上的资料,有一些其他的情况,目前我遇到的是与v-if相关的,仅记录此例。

场景

同一个 div 上,既绑定了 v-else又绑定了一个自定义指令。

  1. 组件刚挂载时,v-if 不满足,显示该 div
  2. 该 div 执行mounted钩子函数时,自定义指令会把该元素移除
  3. 过了一会,v-if条件满足,然后触发 vue 的派发更新,此时 vue 内部抛出异常

测试文件:

html 复制代码
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.global.js"></script>
<div id="app">
  <div v-if="hasData">hasData</div>
  <div v-else v-permission-or="['admin']">else</div>
</div>

<script>
  const { ref, nextTick } = window.Vue
  // 用户的权限只有 dev
  const myPermissions = ['dev']
  // mock 请求
  const request = () => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve()
      }, 500)
    })
  }

  Vue.createApp({
    directives: {
      'permission-or': {
        mounted(el, binding, vnode, prevVnode) {
          if (!myPermissions.includes(binding.value)) {
            // 用户是没有权限的,所以必然会执行此处
            el.remove()
            // 移除后,其父节点为 null
            console.log('el.parentNode', el.parentNode)
          }
        }
      }
    },
    setup() {
      const hasData = ref(false)
      request().then(() => {
        hasData.value = true
      })
      return { hasData }
    }
  }).mount('#app')
</script>

源码分析

首先依赖收集部分就不赘述了,在依赖更新时,会触发effect:

componentUpdateFun函数中,会调用patch函数:

注意这里传入的第三个参数,实际取的是该节点的父元素: 取到的值是什么呢?是null,因为文档中已经不存在该节点了。

继续看,到patch函数内,首先会判断新旧元素是否相同,是则直接 return。这里显然不满足,于是会继续判断isSameVNodeType,该方法的作用是判断是否为同一个节点,我们这里也不是同一个,是要从第二个 div 变为第一个 div,所以isSameVNodeType(n1, n2)为 false,这个if会进去,然后会把旧节点n1设为 null. 继续往下,有个 switch,在这里面会进入default里的第一个if判断中,执行processElement方法,这里传入的container就是外面传进来的父元素null

这个函数就很简单了,判断旧节点是否为null,注意到刚才已经被修改为null了,因此会进入到if内:

然后到mountElement方法中,会执行hostInsert方法:

这个方法的定义处,与之前看到的patch第三个参数的取值,是同一个文件:

问题就在这里了,一路传下来的containernull,到insert方法中,传入了第二个参数parent,于是浏览器报错:

arduino 复制代码
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'insertBefore')

这个错误是 Promise 抛出的,一开始我还查不到日志,因为没有写全局捕获方法。不过即使捕获,也只是提供一下日志罢了,反正 vue 的代码不能继续执行的,所以页面更新也还是异常的。

------为什么是 Promise 抛出的?

因为vue3的依赖更新,本来就是通过 Promise 实现的。就像那道经典题------"nextTick"的原理......

算了,不扯了吧,我也不懂了......

相关推荐
y先森19 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy19 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891122 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐3 小时前
前端图像处理(一)
前端