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"的原理......

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

相关推荐
Jiaberrr6 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀3 小时前
CSS——属性值计算
前端·css
DOKE4 小时前
VSCode终端:提升命令行使用体验
前端
xgq4 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试