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

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

相关推荐
WEI_Gaot3 分钟前
React 19 Props 和 react-icons 和 事件处理函数
前端·react.js
10年前端老司机4 分钟前
微信小程序模板语法和事件
前端·javascript·微信小程序
龙在天4 分钟前
Promise.withResolvers() vs 传统 Promise:谁更胜一筹?
前端
xinhong4 分钟前
Vue3 学习笔记
前端
Evenknow5 分钟前
将"修改源码"改为更专业的"二次开发",体现技术深度
前端·github
khalil5 分钟前
浅析TS枚举与位运算的结合
前端·typescript
yanglei5 分钟前
electron-updater 核心源码解析
前端
神仙别闹8 分钟前
基于Java+MySQL 实现(Web)日程管理系统
java·前端·mysql
布列瑟农的星空10 分钟前
webworker 实践:外部依赖引入和打包问题
前端·低代码
傻小胖12 分钟前
发布一个npm包,更新包,删除包
前端·npm·node.js