Skip to content

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>

参数

参数类型说明
toRouteLocation即将进入的目标路由
fromRouteLocation当前离开的路由

返回值

守卫通过返回值控制导航流向:

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

全局解析守卫

beforeEachbeforeEnter 之后执行,常用于等待异步数据加载完成:

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' }
    }
  }
]

详见 RouteConfig.beforeEnter

组件内离开守卫

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

下一步

Released under the MIT License.