欢迎光临
我们一直在努力

uni-router v1.8.0新增冷启动守卫补执行

v1.8.0 新增 guardRoute() 方法解决冷启动场景下守卫未执行的问题,同时导出 UniApiError / UniApiCause 类型并收紧 NavigationFailure.cause 类型定义

前言

uni-router 的路由守卫(beforeEach / beforeEnter / beforeResolve / afterEach)在通过 router.push / replace / relaunch / back 触发的导航中能完整执行,确保权限校验、登录拦截等逻辑生效。

然而在冷启动场景下,守卫链存在盲区:

  • H5 平台:用户直接在浏览器地址栏输入 URL 访问某个页面
  • 小程序平台:用户通过分享链接、扫码、场景值等直接打开某个页面
  • App 平台:用户通过 deeplink / scheme 直接跳转到某个页面

这些场景下,页面由 uni-app 框架直接加载,不经过路由器的导航流程,守卫未执行。这意味着:用户可能直接进入一个需要登录的页面,而 beforeEach 中的登录校验完全失效。

v1.8.0 通过新增 guardRoute() 方法解决了这一问题:在应用启动后对当前已加载页面补执行一次守卫链,按守卫结果决定是否重定向到安全页面。

同时,本次更新还完善了 uni API 调用失败时的错误类型定义,将原本内部的 UniApiError / UniApiCause 类型导出,并将 NavigationFailure.cause 类型从 unknown 收紧为 UniApiError,让开发者在 catch
中能获得完整的类型提示。


一、问题分析

冷启动守卫盲区

以登录拦截为例,典型的全局守卫如下:

router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !isLoggedIn()) {
next({ name: 'login', query: { redirect: to.fullPath } })
} else {
next()
}
})

在正常导航(router.push('/pages/protected/index'))中,守卫会检查 requireAuth,未登录时重定向到登录页。

但若用户在 H5 端直接在浏览器地址栏输入 https://example.com/#/pages/protected/index:

  • uni-app 框架直接加载 /pages/protected/index 页面(不经过 router.push)
  • 路由器的 beforeEach 守卫从未被调用
  • isLoggedIn() 校验逻辑未执行
  • 用户成功进入受保护页面 —— 权限校验完全失效
  • 这种问题在小程序的扫码进入、App 的 deeplink 跳转中同样存在。

    旧版 API 错误类型的局限

    v1.7.0 及之前,NavigationFailure.cause 类型为 unknown:

    export interface NavigationFailure extends RouterError {
    readonly to: RouteLocation
    readonly from: RouteLocation
    readonly cause?: unknown // ❌ 无法获得类型提示
    }

    当 uni.navigateTo 等 API 调用失败时,cause 实际上是内部 UniApiError 类的实例(包含 api 和 cause.errMsg),但类型为 unknown,开发者无法获得字段提示,需要手动断言:

    try {
    await router.push('/pages/detail/index')
    } catch (e) {
    if (e instanceof NavigationFailure && e.code === RouterErrorCode.NAVIGATION_API_ERROR) {
    const cause = e.cause as UniApiError // ❌ 需要手动断言
    console.log(cause.api) // navigateTo
    console.log(cause.cause.errMsg) // navigateTo:fail …
    }
    }


    二、新增能力

    1. guardRoute() 冷启动守卫检查

    方法签名

    interface GuardRouteOptions {
    /**
    * 守卫中止时的回调
    *
    * 冷启动场景下页面已加载,无法真正"阻止进入"。
    * 当守卫调用 next(false) 中止时,将调用此回调并传入 NavigationFailure 对象。
    * 用户可在此回调中执行 router.relaunch() 等操作跳转到安全页面。
    */

    onAbort?: (failure: NavigationFailure) => void
    }

    class Router {
    guardRoute(location?: RouteLocationRaw, options?: GuardRouteOptions): Promise<RouteLocation>
    }

    行为说明
    守卫结果行为
    守卫放行(next()) 不执行任何导航(页面已加载),resolve 目标路由
    守卫重定向(next(location)) 按守卫指定方式(默认 relaunch,清空栈避免返回受保护页面)跳转到重定向目标
    守卫中止(next(false)) 调用 onAbort 回调(若提供),并 reject NavigationFailure

    guardRoute() 执行完整的守卫链:beforeEach → beforeEnter → beforeResolve(不执行 afterEach,因为未发生实际导航)。

    核心实现

    async guardRoute(location?: RouteLocationRaw, options?: GuardRouteOptions): Promise<RouteLocation> {
    const target = location ? this.matcher.resolve(location) : this.routeState.getCurrentRoute()
    const from = this.routeState.getCurrentRoute()

    // beforeEach
    const beforeResult = await this.guardManager.runBeforeGuards(target, from)
    const handled = this.handleGuardRouteResult(beforeResult, target, from, options)
    if (handled) return handled

    // beforeEnter
    const config = this.matcher.getRouteConfig(target.path)
    if (config?.beforeEnter) {
    const beforeEnterResult = await this.guardManager.runBeforeEnterGuards(target, from, config)
    const handledEnter = this.handleGuardRouteResult(beforeEnterResult, target, from, options)
    if (handledEnter) return handledEnter
    }

    // beforeResolve
    const beforeResolveResult = await this.guardManager.runBeforeResolveGuards(target, from)
    const handledResolve = this.handleGuardRouteResult(beforeResolveResult, target, from, options)
    if (handledResolve) return handledResolve

    // 所有守卫放行,不导航(页面已加载)
    return target
    }

    与 handleGuardResult 不同,handleGuardRouteResult 在守卫放行时不执行导航(页面已加载),仅在重定向时委托给 push / replace / relaunch 执行实际跳转,且重定向默认方式为
    relaunch(而非沿用原始导航方式,因为冷启动场景下没有"原始导航方式")。


    2. UniApiError / UniApiCause 类型导出

    新增类型

    /**
    * uni-app API 失败时的错误原因
    *
    * uni-app 导航 API(navigateTo / redirectTo 等)的 fail 回调始终传入此结构的错误对象。
    */

    export interface UniApiCause {
    /** 错误描述信息 */
    errMsg: string
    }

    /**
    * uni-app API 调用失败的错误信息
    *
    * 包含失败的 API 名称和原始错误原因,作为 NavigationFailure.cause 传递。
    */

    export interface UniApiError {
    /** 调用失败的 API 名称(如 navigateTo / redirectTo) */
    readonly api: string
    /** 原始错误原因 */
    readonly cause: UniApiCause
    }

    NavigationFailure.cause 类型收紧

    export interface NavigationFailure extends RouterError {
    readonly to: RouteLocation
    readonly from: RouteLocation
    /**
    * 原始错误原因
    *
    * 仅当 code 为 NAVIGATION_API_ERROR 时存在,包含失败的 API 名称和原始错误信息。
    */

    readonly cause?: UniApiError // ✅ 从 unknown 收紧为 UniApiError
    }

    isUniApiError() 改为类型守卫

    // v1.7.0:仅返回 boolean
    export function isUniApiError(error: unknown): boolean {
    return error instanceof UniApiError
    }

    // v1.8.0:类型守卫,便于 instanceof 后的类型收窄
    export function isUniApiError(error: unknown): error is UniApiError {
    return error instanceof UniApiError
    }


    三、使用示例

    场景一:App.vue onLaunch 中执行冷启动守卫检查

    最典型的应用场景。在应用启动时对当前已加载页面补执行守卫链,未通过则重定向到首页:

    <script>
    import router from './router'
    import { RouterErrorCode } from '@meng-xi/uni-router'

    export default {
    onLaunch() {
    // 等待路由器初始化完成
    router.isReady().then(() => {
    router
    .guardRoute(undefined, {
    onAbort: failure => {
    console.warn('[guardRoute] 冷启动守卫中止:', failure.code)
    if (failure.code === RouterErrorCode.NAVIGATION_ABORTED) {
    // 守卫中止(如未登录),跳转到首页
    router.relaunch('/pages/index/index')
    }
    }
    })
    .catch(() => {
    // guardRoute 中止时 reject,已在 onAbort 中处理
    })
    })
    }
    }
    </script>

    场景二:守卫重定向自动跳转

    当守卫调用 next(location) 重定向时,guardRoute() 会自动执行跳转,无需在 onAbort 中处理:

    router.beforeEach((to, from, next) => {
    if (to.meta.requireAuth && !isLoggedIn()) {
    // guardRoute() 检测到重定向时,自动用 relaunch 跳转到登录页
    next({ name: 'login', query: { redirect: to.fullPath } })
    } else {
    next()
    }
    })

    // App.vue
    router.isReady().then(() => {
    router.guardRoute() // 未登录用户进入受保护页面时,自动 relaunch 到登录页
    })

    场景三:检查指定路由(非当前页面)

    guardRoute() 可传入 location 参数检查指定路由,而非当前页面:

    // 检查某个特定路由的守卫状态(不导航)
    const result = await router.guardRoute('/pages/protected/index', {
    onAbort: () => {
    console.log('该路由守卫未通过')
    }
    })
    console.log('守卫放行:', result.fullPath)

    场景四:完整捕获 API 错误信息

    v1.8.0 收紧类型后,catch 块中可直接获得 api 和 errMsg 的类型提示:

    import { NavigationFailure, RouterErrorCode } from '@meng-xi/uni-router'

    try {
    await router.push('/pages/detail/index')
    } catch (e) {
    if (e instanceof NavigationFailure && e.code === RouterErrorCode.NAVIGATION_API_ERROR) {
    const cause = e.cause // ✅ 类型为 UniApiError | undefined,无需手动断言
    if (cause) {
    console.log('失败的 API:', cause.api) // navigateTo
    console.log('错误信息:', cause.cause.errMsg) // navigateTo:fail …
    }
    }
    }

    对比旧版(v1.7.0 及之前):

    try {
    await router.push('/pages/detail/index')
    } catch (e) {
    if (e instanceof NavigationFailure && e.code === RouterErrorCode.NAVIGATION_API_ERROR) {
    // ❌ cause 类型为 unknown,无法获得字段提示
    const cause = e.cause as { api: string; cause: { errMsg: string } }
    console.log('失败的 API:', cause.api)
    }
    }


    四、其他优化与修复

    1. ParamValue 类型兼容性增强

    ParamValue 的对象分支从递归 ParamObject 改为 object,兼容 interface 定义的对象类型:

    // v1.7.0
    export type ParamValue = string | number | boolean | null | ParamObject | ParamValue[]
    // ❌ interface 定义的对象没有索引签名,无法赋值给 ParamObject

    // v1.8.0
    export type ParamValue = string | number | boolean | null | undefined | ParamValue[] | object
    // ✅ object 分支兼容所有对象类型,包括 interface 定义的对象

    同时添加 undefined 分支,兼容含可选属性的对象(JSON.stringify 会自动忽略 undefined 属性)。

    2. RouterLink 组件重构

    将 location 计算逻辑提取为 computed,无附加选项(animation / events / persistent 均未传)时直接使用 to,避免无谓的对象包装:

    const location = computed<RouteLocationRaw>(() => {
    // 无附加选项时直接使用 to,避免无谓的对象包装
    if (!props.animation && !props.events && !props.params && props.persistent === undefined) {
    return props.to
    }
    // …合并附加选项
    })

    3. uni API fail 回调类型收紧

    env.d.ts 中各导航 API(navigateTo / redirectTo / switchTab / reLaunch / navigateBack)的 fail 回调参数类型从 unknown 收紧为 UniApiCause:

    // v1.8.0
    interface UniNavigateToOption {
    // …
    fail?: (err: UniApiCause) => void // ✅ 从 unknown 收紧
    }

    4. 守卫混用模式警告

    当守卫同时调用 next() 并返回 Promise 时输出警告:

    // ❌ 不推荐的混用模式
    router.beforeEach((to, from, next) => {
    someAsyncOperation().then(() => {
    next() // next() 在 Promise 中调用
    })
    return somePromise // 同时返回 Promise
    })
    // 控制台警告:Navigation guard "…" called next() and also returned a Promise.
    // Use either next() callback or async/await, not both.

    next() 之后的异步错误会被静默吞掉,开发者应选择其中一种解析模式:next() 回调或 async/await,不可混用。

    5. syncCurrentRoute 参数清理

    移除 syncRoute() 内部未使用的 _from 参数。


    升级指南

    v1.8.0 是向后兼容的新功能版本,无需修改任何现有代码即可升级。

    行为变化

    场景v1.7.0 及之前v1.8.0
    冷启动进入受保护页面 守卫未执行(盲区) 可通过 guardRoute() 补执行守卫链
    NavigationFailure.cause 类型 unknown UniApiError | undefined
    isUniApiError() 返回值 boolean boolean(实现为类型守卫,类型收窄)
    守卫同时用 next() 和 Promise 静默吞掉错误 输出警告
    ParamValue 对象分支 递归 ParamObject object(兼容 interface 对象)

    新增类型导出

    • GuardRouteOptions — guardRoute() 方法的选项类型
    • UniApiCause — uni API fail 回调的错误原因类型
    • UniApiError — uni API 调用失败的错误信息类型

    新增 API

    • router.guardRoute(location?, options?) — 冷启动守卫检查

    冷启动守卫检查最佳实践

    建议在 App.vue 的 onLaunch 中统一处理冷启动守卫检查:

    // App.vue
    onLaunch() {
    router.isReady().then(() => {
    router.guardRoute(undefined, {
    onAbort: failure => {
    // 守卫中止时,重定向到首页或其他安全页面
    if (failure.code === RouterErrorCode.NAVIGATION_ABORTED) {
    router.relaunch('/pages/index/index')
    }
    }
    })
    })
    }

    注意:guardRoute() 仅在应用启动时调用一次即可,无需在每次 onShow 中调用(onShow 触发时可使用 router.syncRoute() 同步路由状态)。

    赞(0)
    未经允许不得转载:171主机测评 » uni-router v1.8.0新增冷启动守卫补执行
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址