Vue Router 4.x风格导航守卫全面升级

v2.2.0 新增 onBeforeRouteLeave 组合式 API,支持组件内通过返回值模式拦截离开导航;同时彻底移除 next() 回调模式,守卫系统全面统一为返回值模式,与 Vue Router 4.x 完全一致。

前言

@meng-xi/uni-router 在 v2.1.0 引入了守卫返回值模式作为推荐方式,同时保留 next() 回调的向后兼容性。v2.2.0 彻底移除 next() 回调支持,守卫系统全面统一为返回值模式,同时新增 onBeforeRouteLeave 组件内离开守卫,实现完整的组件级守卫能力。


一、破坏性变更:彻底移除 next() 回调

变更内容

变更项 v2.1.x v2.2.0
NavigationGuard 签名 (to, from, next?) (to, from)
NavigationGuardNext 类型 存在(已弃用) 移除
NavigationGuardNextOptions 类型 存在(已弃用) 移除
runGuardWithNext() 函数 存在 移除
runGuard() 模式检测 自动检测参数个数 仅返回值模式

迁移对照

旧写法 新写法
next() 不写 return 或 return undefined
next(false) return false
next({ name: 'login' }) return { name: 'login' }
next({ name: 'login' }, { mode: 'replace' }) return { name: 'login' }(mode 不再支持)
next().catch(...) try { return await ... } catch { return false }
next(new Error('msg')) throw new Error('msg') 或 return new Error('msg')

二、新增能力:onBeforeRouteLeave 组件内离开守卫

1. 问题分析

在表单编辑、数据提交等场景中,用户可能误操作离开当前页面导致数据丢失。Vue Router 4.x 提供了 onBeforeRouteLeave 组合式 API 来解决这一问题,允许组件在离开前执行确认逻辑。

1.1 表单编辑场景无法阻止离开

typescript 复制代码
// v2.1.x --- 需要手动注册全局守卫,手动管理移除
let removeGuard: (() => void) | null = null

onMounted(() => {
	removeGuard = router.beforeEach((to, from) => {
		if (from.path === '/pages/edit/edit' && hasUnsavedChanges) {
			return false
		}
	})
})

onBeforeUnmount(() => {
	removeGuard?.()
})

这种方式代码分散、容易遗漏清理,且每个组件都需要重复编写相同的逻辑。

1.2 全局守卫随着组件增多而膨胀

typescript 复制代码
// v2.1.x --- 所有组件的离开逻辑集中在一个全局守卫中
router.beforeEach((to, from) => {
	if (from.path === '/pages/edit/edit' && hasUnsavedChanges) return false
	if (from.path === '/pages/form/form' && formDirty) return false
	if (from.path === '/pages/post/post' && !postSaved) return false
	// ... 随着组件增多,守卫越来越长
})

全局守卫不应关注组件内部状态,组件内守卫更符合"关注点分离"原则。

2. onBeforeRouteLeave 组合式 API

onBeforeRouteLeave 在组件 setup 中调用,注册一个仅在离开当前组件时触发的守卫。组件卸载时自动移除,无需手动清理。

typescript 复制代码
import { onBeforeRouteLeave } from '@meng-xi/uni-router'

onBeforeRouteLeave((to, from) => {
	if (hasUnsavedChanges) {
		return false // 中止导航
	}
	// 不返回值或 return true 表示放行
})

返回值对照表

返回值 行为
undefined / void / true 放行,允许离开
false 中止导航(NAVIGATION_ABORTED)
RouteLocationRaw 重定向到其他路由
Error 对象 取消导航(NAVIGATION_CANCELLED)
抛出异常 取消导航(NAVIGATION_CANCELLED)

3. 异步离开确认

支持异步守卫,结合 uni.showModal 实现确认对话框:

typescript 复制代码
import { onBeforeRouteLeave } from '@meng-xi/uni-router'

onBeforeRouteLeave((to, from) => {
	if (hasUnsavedChanges) {
		return new Promise(resolve => {
			uni.showModal({
				title: '提示',
				content: '有未保存的修改,确认离开?',
				success: res => resolve(res.confirm ? true : false)
			})
		})
	}
})

4. RouteLeaveGuard 类型

typescript 复制代码
type RouteLeaveGuard = (to: RouteLocation, from: RouteLocation) => NavigationGuardReturn | Promise<NavigationGuardReturn>

三、实现原理

onBeforeRouteLeave 内部通过 router.beforeEach 注册一个全局前置守卫,但只在 from 匹配当前组件路径时执行用户守卫:

typescript 复制代码
export function onBeforeRouteLeave(guard: RouteLeaveGuard): void {
	const router = useRouter()
	const route = useRoute()
	const fromPath = route.value.path

	// 注册全局前置守卫,仅在 from 匹配当前组件路径时执行
	const remove = router.beforeEach((to, from) => {
		if (from.path !== fromPath) return // 不匹配时自动放行
		return guard(to, from) // 执行用户守卫
	})

	// 组件卸载时自动移除守卫
	onBeforeUnmount(remove)
}

执行流程

javascript 复制代码
组件 setup
  └── onBeforeRouteLeave(guard)
        └── router.beforeEach((to, from) => {
              ├── from.path !== fromPath → 放行(不执行 guard)
              └── from.path === fromPath → 执行 guard(to, from)
                    ├── guard return undefined / true → 放行
                    ├── guard return false            → 中止导航
                    ├── guard return RouteLocationRaw → 重定向
                    ├── guard return Error            → 取消导航
                    └── guard 抛出异常                 → 取消导航
            })
        └── onBeforeUnmount(remove)  // 组件卸载时自动移除

重要限制

onBeforeRouteLeave 只能拦截经过路由器 push / replace / back / relaunch 的导航,无法拦截以下场景:

  • 物理返回键(Android 系统返回键)
  • 侧滑返回手势(iOS 屏幕左滑)
  • 浏览器后退按钮(H5)
  • 小程序左上角返回按钮
  • H5 平台的 uni.switchTab(因 interceptUniApi 对 switchTab 采用"放行原始调用 + success 回调同步状态"策略)

对于这些场景,需要在 onShow 中通过 syncRoute() 同步状态后做事后处理。


四、完整使用示例

场景一:表单编辑离开确认

typescript 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteLeave } from '@meng-xi/uni-router'

const formDirty = ref(false)

function markDirty() {
  formDirty.value = true
}

// 同步离开确认
onBeforeRouteLeave(() => {
  if (formDirty.value) {
    uni.showToast({ title: '有未保存的修改,已阻止离开', icon: 'none' })
    return false
  }
})
</script>

场景二:异步确认对话框

typescript 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteLeave } from '@meng-xi/uni-router'

const hasUnsavedChanges = ref(false)

onBeforeRouteLeave(() => {
  if (hasUnsavedChanges.value) {
    return new Promise((resolve) => {
      uni.showModal({
        title: '确认离开',
        content: '有未保存的修改,确定要离开吗?',
        success: (res) => resolve(res.confirm)
      })
    })
  }
})
</script>

场景三:离开时保存数据

typescript 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteLeave } from '@meng-xi/uni-router'

const draft = ref('')
const isSaving = ref(false)

onBeforeRouteLeave(async () => {
  if (draft.value) {
    isSaving.value = true
    try {
      await saveDraft(draft.value)
      // 保存成功,放行
    } catch {
      // 保存失败,阻止离开
      return false
    } finally {
      isSaving.value = false
    }
  }
})
</script>

五、升级指南

新增导出

typescript 复制代码
// 组合式 API
export { onBeforeRouteLeave } from '@meng-xi/uni-router'

// 类型
export type { RouteLeaveGuard } from '@meng-xi/uni-router'

推荐迁移

如果之前使用全局守卫模拟组件内离开逻辑,可迁移到 onBeforeRouteLeave:

typescript 复制代码
// 迁移前 --- 手动管理全局守卫
const removeGuard = router.beforeEach((to, from) => {
	if (from.path === '/pages/edit/edit' && hasUnsavedChanges) {
		return false
	}
})
onBeforeUnmount(removeGuard)

// 迁移后 --- 组件内离开守卫
onBeforeRouteLeave(() => {
	if (hasUnsavedChanges) {
		return false
	}
})

版本兼容性

功能 v2.1.x v2.2.0
onBeforeRouteLeave 不支持 支持
RouteLeaveGuard 类型 不存在 新增
守卫返回值模式 支持 支持
next() 回调模式 支持(已弃用) 不支持
afterEach 接收 failure 支持 支持
相关推荐
IT_陈寒1 小时前
JavaScript闭包的这个坑,我居然今天才爬出来
前端·人工智能·后端
可乐鸡翅yeah_1 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
葡萄城技术团队1 小时前
复制走的是数字,留下的是身份:Web 表格里被忽视的数据断层
前端
kyriewen1 小时前
我实测了前端日期的 5 个坑:差 8 小时只是开始
前端·javascript·程序员
测试运维日常笔记1 小时前
大模型应用专项测试指南:从 Prompt、RAG 到 Agent 的完整链路
前端·prompt
计算机魔术师2 小时前
没有人告诉他们要这么做——AI 智能体自己学会了"组团"越狱
前端
a努力。2 小时前
LangChain Agent消息链路全解析
java·前端·javascript
今年下半年2 小时前
【前端】ant-design-vue 表格「行、列都动态」的处理方案
前端·javascript·vue.js
szarron3 小时前
VNA6 便携式矢量网络分析仪|1MHz‑6.3GHz 手持 VNA 真实应用场景全解析
前端·嵌入式硬件·信息可视化·数据挖掘·数据分析
Reisentyan3 小时前
B站首页,点击视频卡片之后首页自动刷新的问题
前端