关于 Vben5 热更新 Cannot read properties of null (reading 'nextSibling')的分析

问题

保存时仍需报错 但是F5正常 打包也正常

ini 复制代码
chunk-ZHNFVA5M.js?v=ac948eb1:11432 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'nextSibling')
    at nextSibling (chunk-ZHNFVA5M.js?v=ac948eb1:11432:35)
    at removeFragment (chunk-ZHNFVA5M.js?v=ac948eb1:6679:14)
    at remove2 (chunk-ZHNFVA5M.js?v=ac948eb1:6650:9)
    at unmount (chunk-ZHNFVA5M.js?v=ac948eb1:6628:9)
    at Object.remove (chunk-ZHNFVA5M.js?v=ac948eb1:9303:13)
    at unmount (chunk-ZHNFVA5M.js?v=ac948eb1:6603:20)
    at unmountComponent (chunk-ZHNFVA5M.js?v=ac948eb1:6713:7)
    at unmount (chunk-ZHNFVA5M.js?v=ac948eb1:6593:7)
    at unmountComponent (chunk-ZHNFVA5M.js?v=ac948eb1:6713:7)
    at unmount (chunk-ZHNFVA5M.js?v=ac948eb1:6593:7)

环境准备

官方原版前端环境(5.5.9版本) 未调整任何配置 具体为

  • transition开
  • keepAlive关

(后话) 跟上面的都没关系

在什么情况才会出现

之前在项目中测试 只要是xxx.ts(tsx)保存 就会出现该问题 xxx.vue则不会出现

测试非useVbenXXX的页面

先使用playground/src/views/demos/access/index.vue来测试

新建data.ts 然后vue文件引用

data.ts

js 复制代码
console.log('code code');

index.vue

arduino 复制代码
import './data';

修改data.ts里的代码(或者直接保存也会触发热更新) 页面正常热更新 没有出现问题

测试useVbenModal/Drawer

文件位置playground/src/views/examples/modal/index.vue

还是同样 引用data.ts

流程跟上面一样 也能正常热更新

测试useVxe

文件位置playground/src/views/examples/vxe-table/basic.vue

data.ts

js 复制代码
console.log('code code');

export const column = [
  { title: '序号', type: 'seq', width: 50 },
  { field: 'name', title: 'Name' },
  { field: 'age', sortable: true, title: 'Age' },
  { field: 'nickname', title: 'Nickname' },
  { field: 'role', title: 'Role' },
  { field: 'address', showOverflow: true, title: 'Address' },
];

vue

js 复制代码
onMounted(() => {
  console.log('mounted');
});

onBeforeUnmount(() => {
  console.log('unmount');
});

然后保存data.ts 也能正常更新 看看Log

卸载再挂载 看起来流程没有问题

换个符合业务带查询的表格试试呢 playground/src/views/examples/vxe-table/form.vue

保存也是正常的 (后话: 因为没有Modal/Drawer) 也没有公用data.ts

混合

使用这个页面测试

保存data.ts后 一定会复现

先把drawer相关的代码移除 发现保存正常了(热更新)

那么确定是useForm的问题?

然后试了 单独 drawer+form 的情况(不包含表格) 没问题?

这就奇了怪了 三个单独使用都没问题 混在一次就有问题了?

然后回到role菜单来测试 保留drawer 但是去除useForm 这样热更新也没问题

只保留一个空drawer 难道是useForm的问题 控制变量进一步探究

js 复制代码
<script lang="ts" setup>
import type { SystemRoleApi } from '#/api/system/role';

import { computed, ref } from 'vue';

import { useVbenDrawer } from '@vben/common-ui';

import { $t } from '#/locales';

const formData = ref<SystemRoleApi.SystemRole>();

const [Drawer] = useVbenDrawer({});
</script>
<template>
  <Drawer />
</template>

研究过程不赘述 发现将schema改为内部(不引用data.ts) 则不报错

js 复制代码
原版 从外部data.ts导入 报错
const [Form, formApi] = useVbenForm({
  schema: useFormSchema(),
  showDefaultActions: false,
});

内部 不报错
const [Form, formApi] = useVbenForm({
  schema: [],
  showDefaultActions: false,
});

最终发现 只要把data.ts中引用的schema移动到新的ts 或者 直接在modal.vue 都能正常运行(算一种解决方案)

js 复制代码
const [BasicForm, formApi] = useVbenForm({
  commonConfig: {
    labelWidth: 80,
  },
  schema: [直接在这里写 不从data.ts导入],
  showDefaultActions: false,
});

或者

js 复制代码
// 跟data.ts文件分开
import { modalSchema } from './other';

const [BasicForm, formApi] = useVbenForm({
  commonConfig: {
    labelWidth: 80,
  },
  schema: modalSchema(),
  showDefaultActions: false,
});

这是一种解决方案 但不是最终的解决方案

另外的解决方案

使用热更新api来操作 也能正常 不推荐

html 复制代码
<script setup lang="ts">
const key = ref(0);
if (import.meta.hot) {
  import.meta.hot.accept(() => {
    key.value++;
  });
}
</script>

<template>
  <BasicModal :key="key" :title="title" class="w-[550px]">
    <BasicForm />
  </BasicModal>
</template>

继续分析

问题跟useForm也无关 去除form后保存data.ts 热更新还是报错 那么问题就是在modal(drawer)上

那么需要确定是modal造成的还是useVbenModal造成

使用控制变量的方法 直接不使用useVbenModal方法 导入modal并且更改props来直接显示

bash 复制代码
packages/@core/ui-kit/popup-ui/src/modal/modal.vue

发现保存 热更新也是在报错 那么确定就是组件的问题

通过一个个删除元素 最终确认!!!

问题出在这个组件 DialogContent!!!

内部是这个组件 radix-vue提供的DialogPortal 确认是这个组件产生的问题

只要这个删除(换成div) 热更新就不会报错(当然样式也会有异常)

到github查看源码

github.com/unovue/reka...

使用的是TeleportPrimitive 问题出在这里 查看源码

贴上关键代码

js 复制代码
<script setup lang="ts">
import { useMounted } from '@vueuse/core'

withDefaults(defineProps<TeleportProps>(), {
  to: 'body',
})

const isMounted = useMounted()
</script>

<template>
  <Teleport
    v-if="isMounted || forceMount"
    :to="to"
    :disabled="disabled"
    :defer="defer"
  >
    <slot />
  </Teleport>
</template>

本质就是对Teleport的封装(且参数都和Vue的一致) 看起来也没啥问题 那直接把上面的组件换成vue的Teleport试试 发现

热更新正常了 不会报错了!!!

而且外部只使用了to参数 切换到teleport也是无缝

after

经过测试 热更新不会出现问题

原因分析

将文件粘贴到本地来测试

DialogContent.vue 引用 DialogPortal.vue 引用 MyTeleport.vue

保存 果然出现了问题

然后直接引用MyTeleport.vue来测试

也报错

当直接使用Teleport是正常了 只要包装了一层 就会产生问题

目前看使用Teleport替换也没有产生什么副作用 先这样吧 有空再分析补充

解决方案

packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue

DialogPortal替换为Teleport

Drawer也是一样的改法

packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetContent.vue

DialogPortal替换为Teleport

相关推荐
Dragon Wu20 分钟前
前端 下载后端返回的二进制excel数据
前端·javascript·html5
北海几经夏26 分钟前
React响应式链路
前端·react.js
晴空雨1 小时前
React Media 深度解析:从使用到 window.matchMedia API 详解
前端·react.js
一个有故事的男同学1 小时前
React性能优化全景图:从问题发现到解决方案
前端
探码科技1 小时前
2025年20+超实用技术文档工具清单推荐
前端
Juchecar1 小时前
Vue 3 推荐选择组合式 API 风格(附录与选项式的代码对比)
前端·vue.js
uncleTom6661 小时前
# 从零实现一个Vue 3通用建议选择器组件:设计思路与最佳实践
前端·vue.js
影i1 小时前
iOS WebView 异步跳转解决方案
前端
Nicholas681 小时前
flutter滚动视图之ScrollController源码解析(三)
前端
爪洼守门员1 小时前
安装electron报错的解决方法
前端·javascript·electron