NavigationGuard
导航守卫函数类型,用于在导航发生前/后执行校验、重定向、埋点等逻辑。
类型定义
ts
type NavigationGuardReturn = void | undefined | boolean | RouteLocationRaw | NavigationRedirect | Error | null
interface NavigationRedirect {
location: RouteLocationRaw
mode?: 'push' | 'replace' | 'relaunch'
}
type NavigationGuard = (
to: RouteLocation,
from: RouteLocation,
) => NavigationGuardReturn | Promise<NavigationGuardReturn>参数
| 参数 | 类型 | 说明 |
|---|---|---|
to | RouteLocation | 即将进入的目标路由 |
from | RouteLocation | 当前离开的路由 |
返回值
守卫通过返回值控制导航流向:
ts
router.beforeEach(async (to, from) => {
if (isLoggedIn()) return true
return { name: 'login' }
})抛出错误
ts
// 抛出错误
throw new Error('权限不足')
// 或
return new Error('权限不足')错误会被 router.onError 捕获,并中止导航。
守卫类型分类
全局前置守卫
ts
const removeGuard = router.beforeEach((to, from) => {
// 权限校验、登录检查、埋点等
if (to.meta.requireAuth && !isLoggedIn()) {
return { name: 'login', query: { redirect: to.fullPath } }
}
return true
})
// 移除守卫
removeGuard()全局解析守卫
在 beforeEach 和 beforeEnter 之后执行,常用于等待异步数据加载完成:
ts
router.beforeResolve(async (to) => {
if (to.meta.preload) {
await store.preloadData(to.meta.preload)
}
return true
})全局后置钩子
导航完成后执行,不接受 next 参数,无法改变导航流向:
ts
router.afterEach((to, from) => {
// 设置标题
if (to.meta.title) {
uni.setNavigationBarTitle({ title: to.meta.title as string })
}
// 页面埋点
trackPageView(to.path)
})路由独享守卫
通过 RouteConfig.beforeEnter 配置,仅对该路由生效:
ts
const routes = [
{
path: 'pages/admin/admin',
name: 'admin',
beforeEnter: (to, from) => {
if (hasRole('admin')) return true
return { name: '403' }
}
}
]组件内离开守卫
通过 onBeforeRouteLeave 在组件 <script setup> 中注册离开守卫,组件卸载时自动移除:
ts
type RouteLeaveGuard = (to: RouteLocation, from: RouteLocation) => NavigationGuardReturn | Promise<NavigationGuardReturn>ts
import { onBeforeRouteLeave } from '@meng-xi/uni-router'
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges()) {
uni.showModal({
title: '提示',
content: '有未保存的修改,确定离开吗?',
success: (res) => {
if (res.confirm) {
// 用户确认离开,放行
return true
}
}
})
return false // 中止导航
}
return true
})返回守卫
通过 router.onBeforeBack 注册,在返回操作触发时执行(App 物理返回键 / 导航栏返回 / uni.navigateBack,H5 浏览器后退 / 后退手势,router.back()):
ts
type BackGuardReturn = boolean | void | Promise<boolean | void>
type BackGuard = (to: RouteLocation, from: RouteLocation) => BackGuardReturn- 返回
false阻止返回;true/undefined放行 - 支持异步(Promise)
- 返回守卫放行后复用
beforeEach → beforeResolve链路
ts
router.onBeforeBack((to, from) => {
if (hasUnsavedChanges()) return false // 阻止返回
// 不返回值或 return true 放行
})
// 移除守卫
const remove = router.onBeforeBack(guard)
remove()平台支持
App 通过 onBackPress、H5 通过 popstate 事件接入返回守卫;小程序原生返回(胶囊/物理键/滑动)无法拦截。iOS 侧滑需配合 app.setSideSlipGesture('none') 禁用手势。详见守卫 - 返回守卫。
执行顺序
完整导航的守卫执行顺序:
1. beforeEach(全局前置守卫,按注册顺序)
↓
2. beforeEnter(路由独享守卫,按数组顺序)
↓
3. beforeResolve(全局解析守卫,按注册顺序)
↓
4. 导航确认,执行 uni 原生跳转
↓
5. afterEach(全局后置钩子,按注册顺序)返回操作(物理返回键 / 浏览器后退 / router.back())的执行顺序为 onBeforeBack → beforeEach → beforeResolve → uni.navigateBack → afterEach,即返回守卫先于全局前置守卫执行。
守卫中止后的行为
- 任一守卫返回
false或抛出错误:导航中止,后续守卫不执行 - 任一守卫返回重定向:重新走完整流程(从
beforeEach开始) afterEach不受影响:仅在导航确认后执行,无法中止
可控重定向
通过返回 { location, mode } 对象,可同时指定重定向目标和重定向使用的导航方式:
ts
router.beforeEach((to, from) => {
if (to.meta.requireAuth && !isLoggedIn()) {
// 用 replace 跳转登录页,避免登录页残留在页面栈中
return { location: { name: 'login', query: { redirect: to.fullPath } }, mode: 'replace' }
}
})mode 取值
| mode | 对应 uni API | 适用场景 |
|---|---|---|
'push' | uni.navigateTo | 登录后需返回原页面,保留目标页在栈中 |
'replace' | uni.redirectTo | 替换当前页,不留历史(如登录页) |
'relaunch' | uni.reLaunch | 清空栈(如权限不足回首页) |
行为规则
- 显式
mode优先于原始导航方式 - 未指定
mode时沿用原始导航方式(back触发时回退为relaunch) location支持路径字符串、路径对象或命名对象
Promise 式返回值
ts
type NavigationGuardReturn = void | undefined | boolean | RouteLocationRaw | NavigationRedirect | Error | null| 返回值 | 说明 |
|---|---|
undefined / void | 放行 |
null | 放行 |
true | 放行 |
false | 中止 |
RouteLocationRaw | 重定向(沿用原始导航方式) |
NavigationRedirect | 重定向并指定导航方式 |
Error | 抛出错误,中止导航 |
ts
// 放行
router.beforeEach(() => {})
// 中止
router.beforeEach(() => false)
// 重定向(沿用原始导航方式)
router.beforeEach(() => ({ name: 'login' }))
// 重定向(replace 方式)
router.beforeEach(() => ({ location: { name: 'login' }, mode: 'replace' }))
// 抛出错误
router.beforeEach(() => {
return new Error('权限不足')
})实战示例
登录校验
ts
router.beforeEach((to, from) => {
const isLoggedIn = !!uni.getStorageSync('token')
if (to.meta.requireAuth && !isLoggedIn) {
// 重定向到登录页,使用 replace 避免登录页残留在页面栈中
return { location: { name: 'login', query: { redirect: to.fullPath } }, mode: 'replace' }
}
return true
})权限校验
ts
// 类型增强
declare module '@meng-xi/uni-router' {
interface RouteMeta {
roles?: string[]
}
}
router.beforeEach((to) => {
if (to.meta.roles) {
const userRoles = getUserRoles()
if (!to.meta.roles.some(r => userRoles.includes(r))) {
uni.showToast({ title: '无权访问', icon: 'none' })
return false
}
}
return true
})异步数据预加载
ts
router.beforeResolve(async (to) => {
if (to.name === 'detail') {
try {
await store.fetchDetail(to.query.id)
} catch (err) {
uni.showToast({ title: '加载失败', icon: 'none' })
return false
}
}
return true
})页面埋点
ts
declare module '@meng-xi/uni-router' {
interface RouteMeta {
trackName?: string
}
}
router.afterEach((to, from) => {
if (to.meta.trackName) {
trackPageView(to.meta.trackName, {
from: from.path,
to: to.path,
duration: Date.now() - pageStartTime
})
}
pageStartTime = Date.now()
})动态标题
ts
router.afterEach((to) => {
const title = to.meta.title as string | undefined
uni.setNavigationBarTitle({ title: title || '默认标题' })
})防止重复导航
ts
let isNavigating = false
router.beforeEach((to, from) => {
if (isNavigating) {
return false
}
isNavigating = true
return true
})
router.afterEach(() => {
isNavigating = false
})常见问题
守卫中可以访问组件实例吗?
beforeEach/beforeResolve:不可以,此时目标组件尚未创建afterEach:不可以,但可以通过getCurrentPages()获取页面实例beforeRouteEnter:不支持,该守卫已从核心库中移除,请使用其他守卫替代
守卫中抛出异常会怎样?
异常会被 router.onError 捕获,并中止当前导航:
ts
router.onError((err, to, from) => {
console.error('导航错误:', err)
uni.showToast({ title: '页面加载失败', icon: 'none' })
})
router.beforeEach(async (to) => {
if (to.meta.requireAuth) {
const user = await fetchUser() // 可能抛出网络错误
if (!user) return { name: 'login' }
}
return true
})下一步
- 路由守卫指南 — 守卫的深入讲解
- Router 实例 — 注册守卫的方法
- RouterError 类型 — 错误处理
