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

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

相关推荐
EndingCoder6 分钟前
React从基础入门到高级实战:React 基础入门 - 简介与开发环境搭建
前端·javascript·react.js·前端框架
劲爽小猴头31 分钟前
HTML5快速入门-常用标签及其属性(三)
前端·html·html5
二十雨辰2 小时前
[CSS3]Flex布局
前端·html·css3
小镇学者2 小时前
【JS】Vue 3中ref与reactive的核心区别及使用场景
前端·javascript·vue.js
xosg2 小时前
HTMLUnknownElement的使用
java·前端·javascript
xosg2 小时前
如何选用正确的html元素
前端·html
周之鸥2 小时前
html主题切换小demo
前端·html
Code_Geo3 小时前
python中Web框架Flask vs FastAPI 对比分析
前端·python·flask
打小就很皮...3 小时前
深入探索 CSS 中的伪类:从基础到实战
前端·css
Lhuu(重开版4 小时前
Vue:axios(GET请求)
前端·javascript·vue.js