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.xv2.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 表单编辑场景无法阻止离开
// 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 全局守卫随着组件增多而膨胀
// 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 中调用,注册一个仅在离开当前组件时触发的守卫。组件卸载时自动移除,无需手动清理。

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 实现确认对话框:

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 类型

type RouteLeaveGuard = (to: RouteLocation, from: RouteLocation) => NavigationGuardReturn | Promise<NavigationGuardReturn>

三、实现原理

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

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)
}

执行流程

组件 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() 同步状态后做事后处理。


四、完整使用示例

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

<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>

场景二:异步确认对话框

<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>

场景三:离开时保存数据

<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>

五、升级指南

新增导出

// 组合式 API
export { onBeforeRouteLeave } from '@meng-xi/uni-router'

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

推荐迁移

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

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

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

版本兼容性

功能v2.1.xv2.2.0
onBeforeRouteLeave不支持支持
RouteLeaveGuard 类型不存在新增
守卫返回值模式支持支持
next() 回调模式支持(已弃用)不支持
afterEach 接收 failure支持支持
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦曦i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值