vue-router离开页面报错无法读取null的parentNode属性原因
根本原因
报错和后续路由失效是三个问题叠加导致的:
- 两个watcher形成了闭环触发链:第一个watcher监听路由query变化后修改本地筛选响应式变量,第二个watcher监听本地变量变化后又调用
router.replace修改路由query,本身就存在循环触发的风险。 - 页面离开时的导航流程被打断:当触发离开当前页的路由跳转时,组件进入卸载流程,此时路由切换会先改变
route.query的值,触发第一个watcher修改本地变量,本地变量变化马上触发第二个watcher调用router.replace,直接中断了正在进行的离开导航。此时vue-router内部维护的组件实例、DOM节点引用已经在卸载流程中被置空,后续DOM操作访问null节点的parentNode属性就抛出如下类型错误,同时路由内部的pending导航状态被卡死,后续点击链接只会更新地址栏URL,不会触发实际的组件跳转渲染:
TypeError: Cannot read properties of null (reading 'parentNode')
- 代码存在低级笔误:第二个watcher中更新query时写的变量名是
selectedSortORder(OR两个字母大写),和实际定义的selectedSortOrder变量名不匹配,会导致取值为undefined,触发非预期的watcher执行。另外第二个watcher的回调无任何异步逻辑却加了async关键字,会额外返回Promise对象,干扰路由的导航状态判断。
修复方案
按以下步骤调整即可解决问题:
- 修正变量名拼写错误,移除回调上多余的
async关键字 - 加同步锁切断watcher的循环触发链:从路由同步数据到本地变量时,暂时锁闭本地变量写回路由的逻辑
- 增加组件挂载状态标记,组件卸载后不再执行路由更新操作,避免打断正常导航
- 执行
router.replace前先对比目标query和当前query是否一致,一致则跳过更新,减少无意义的路由操作
修复后的完整代码参考:
import { ref, watch, onUnmounted } from 'vue' import { useRoute, useRouter } from 'vue-router' const route = useRoute() const router = useRouter() // 同步锁,避免循环触发 const isSyncingFromRoute = ref(false) // 组件挂载标记 const isComponentAlive = ref(true) onUnmounted(() => { isComponentAlive.value = false }) watch( () => route.query, () => { // 加锁,阻止本地变量更新触发路由写回 isSyncingFromRoute.value = true selectedAddresses.value = selectedAddressesQuery.value selectedStatuses.value = selectedStatusesQuery.value selectedSortOrder.value = selectedSortOrderQuery.value // 赋值完成后等当前同步流程执行完再释放锁 Promise.resolve().then(() => { isSyncingFromRoute.value = false }) }, { deep: true } ) watch( () => [ selectedAddresses.value, selectedStatuses.value, selectedSortOrder.value, ], () => { // 组件已卸载、或者当前是从路由同步数据的过程中,直接跳过 if (!isComponentAlive.value || isSyncingFromRoute.value) return const targetQuery = { ...route.query, address: selectedAddresses.value, sort: selectedSortOrder.value, // 修正拼写错误 status: selectedStatuses.value, } // 对比query是否一致,一致则不触发路由更新 const queryEqual = Object.keys(targetQuery).every( key => String(route.query[key]) === String(targetQuery[key]) ) && Object.keys(route.query).every( key => Object.prototype.hasOwnProperty.call(targetQuery, key) ) if (queryEqual) return router.replace({ query: targetQuery }) } )
内容的提问来源于stack exchange,提问作者Soviut
相关产品推荐
相关产品推荐

