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(因 interceptUniApiswitchTab 采用"放行原始调用 + 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 支持 支持
相关推荐
cindershade1 小时前
前端大文件上传完整方案:分片上传、断点续传、秒传与失败重试
前端
HjhIron1 小时前
NestJS 入门指南:从工厂模式到模块化 CRUD 实战
前端·nestjs
HjhIron1 小时前
手把手教你用 Next.js 14 + Redis 从零搭建一个全栈 Markdown 笔记系统
前端·全栈·next.js
WIN赢1 小时前
【抽象思想-从复杂中抽离简单、收敛的口子】
java·前端·javascript
martindelophy1 小时前
Codex Chrome 插件 + Timeline Studio:构建可编辑的 AI 视频剪辑 Agent 工作流
前端·人工智能·chrome
whyutianict_vv2 小时前
从 Web 前端到 HarmonyOS ArkTS:一次 AI 鸿蒙全栈智能体开发的迁移实录
前端·人工智能·harmonyos
qziovv3 小时前
前端转flutter——项目架构、初始化
前端·flutter
Ali885203 小时前
Python字符串方法速查表大全
前端·python