应用路由事件用于从应用级别监听主页面路由的开始和完成,也可以在路由真正执行前重写目标页面。
它适合处理路由上报、全局状态重置、访问控制、无效入口页纠正和页面迁移等与“一次路由”相关的逻辑。如果逻辑只与单个页面的创建、显示或销毁有关,应优先使用对应的页面生命周期。
应用路由事件只作用于主页面路由。dialogPage 的打开、关闭以及随所属页面销毁,均不会触发本页 API。
| API | 说明 |
|---|---|
uni.onBeforeAppRoute | 监听路由执行前的事件 |
uni.offBeforeAppRoute | 取消监听路由执行前的事件 |
uni.onAppRoute | 监听路由成功后的事件 |
uni.offAppRoute | 取消监听路由成功后的事件 |
uni.rewriteRoute | 在路由执行前重写本次路由 |
一次成功路由的主要执行顺序如下:
发起路由
-> 解析路径并校验目标
-> onBeforeAppRoute
-> 执行路由逻辑
-> 目标页面 onShow
-> onAppRoute
-> 路由转场动画完成
新页面创建时,相关事件和页面生命周期的顺序为:
onBeforeAppRoute -> onLoad -> onShow -> onAppRoute
返回已有页面或切换到已存在的 tabBar 页面时,不会再次触发 onLoad,顺序为:
onBeforeAppRoute -> onShow -> onAppRoute
onBeforeAppRoute 在页面栈变化以及页面创建、销毁等实际副作用发生前触发。onAppRoute 在路由成功且目标页面的 onShow 执行后触发,不等待路由转场动画完成。
| 场景 | onBeforeAppRoute | onAppRoute | 可重写 |
|---|---|---|---|
| 应用启动并进入有效首页或二级页面 | 触发 | 路由成功后触发 | 是 |
| 应用直接启动到不存在的页面 | 触发,notFound 为 true;支付宝小程序固定为 false | 未重写时按缺页流程触发;支付宝小程序不触发 | 支持 rewriteRoute 的平台可重写 |
navigateTo、redirectTo、reLaunch 成功 | 触发 | 路由成功后触发 | 是 |
| 切换到其他 tabBar 页面 | 触发 | 路由成功后触发 | 是,且目标必须是 tabBar 页面 |
switchTab 到当前 tabBar 页面 | 不触发 | 不触发 | 否 |
navigateBack、系统返回、返回手势或 Web History 后退 | 触发 | 路由成功后触发 | 否 |
| 路由 API 参数错误或目标页面校验失败 | 不触发;支付宝小程序仍会触发,且 notFound 为 false | 不触发 | 否 |
onBeforeAppRoute 已触发,但路由随后取消或执行失败 | 已触发 | 不触发 | 仅可在同步回调阶段重写 |
| 应用从后台恢复,但主页面路由未变化 | 不触发 | 不触发 | 否 |
| dialogPage 打开、关闭或随所属页面销毁 | 不触发 | 不触发 | 否 |
监听器注册前已经发生的事件不会补发。如需监听或重写 appLaunch,应在 onLaunch 生命周期中或之前尽早注册监听器。
onBeforeAppRoute 和 onAppRoute 的公共事件参数含义如下:
| 属性 | 说明 |
|---|---|
path | 目标页面路径,不包含开头的 / |
query | 当前轮路由解析得到的页面参数 |
openType | 路由类型。发生重写时保持原路由类型不变 |
notFound | 当前目标页面是否不存在 |
routeEventId | 应用实例内唯一的路由事件标识 |
onAppRoute 还会提供 timeStamp,表示当前轮路由事件生成时的时间戳。
不同平台可能提供额外的事件字段。编写跨平台代码时,应只依赖本节列出的公共字段。
未发生重写时,同一次路由的 onBeforeAppRoute 与 onAppRoute 使用相同的 routeEventId。每次成功重写都会生成新的路由事件和新的 routeEventId,最终的 onAppRoute 使用最后一轮 onBeforeAppRoute 的 routeEventId。
正常调用路由 API 时,参数错误或目标页面不存在会直接失败。除支付宝小程序外,此类失败不会产生应用路由事件。notFound 主要用于应用直接启动到不存在页面等已经进入路由流程的场景。
openType | 路由来源 |
|---|---|
appLaunch | 应用首次启动;Web 直接访问首页或二级页面等入口路由 |
navigateTo | uni.navigateTo;Web History 前进;无法识别为其他类型的新增页面路由 |
navigateBack | uni.navigateBack;系统返回、返回手势;Web History 后退;小程序系统返回 |
redirectTo | uni.redirectTo |
reLaunch | uni.reLaunch |
switchTab | uni.switchTab;用户切换到其他 tabBar 页面 |
平台差异
rewriteRoute 要求基础库 3.8.0 及以上版本,并受微信客户端版本、运行平台和分包限制。微信开发者工具模拟器不支持 rewriteRoute,应在支持该能力的真机环境中验证。my.createRouteObserver 实现路由事件监听。支付宝小程序开发者工具目前未提供 my.createRouteObserver,无法在开发者工具中验证路由事件监听相关 API,应在支持该能力的支付宝客户端真机环境中验证。支付宝的前置事件不提供目标页面是否存在的信息,因此 onBeforeAppRoute 的 notFound 固定为 false。当路由 API 的目标页面不存在时,支付宝仍会触发 onBeforeAppRoute,但不会触发 onAppRoute;应用直接启动到不存在的页面时同样不会触发 onAppRoute。支付宝小程序暂不支持 rewriteRoute。监听应用路由成功后的事件。
| Web | 微信小程序 | Android | iOS | HarmonyOS |
|---|---|---|---|---|
| 5.25 | 5.25 | 5.25 | 5.25 | 5.25 |
| 名称 | 类型 | 必填 | 描述 |
|---|---|---|---|
| callback | (event: AppRouteEvent) => void | 是 | 应用路由事件回调 |
| 名称 | 类型 | 必备 | 兼容性 | 描述 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| path | string | 是 | 路由页面路径,不包含开头的斜杠 | ||||||||
| query | UTSJSONObject | 是 | 路由页面参数 | ||||||||
| openType | string | 是 | 应用路由类型。 | ||||||||
| |||||||||||
| notFound | boolean | 是 | 路由页面是否不存在。支付宝小程序不提供该信息,固定为 false | ||||||||
| timeStamp | number | 是 | 事件触发时的时间戳 | ||||||||
| routeEventId | string | 是 | 路由事件唯一标识 | ||||||||
| page | IAnyObject | 否 | 当前打开页面的相关配置 | ||||||||
| pipMode | string | 否 | 可选值: - 'min': 视频页面缩小为小窗; - 'max': 视频小窗还原为页面; | ||||||||
| |||||||||||
| renderer | string | 否 | 渲染引擎 可选值: - 'webview': Webview 渲染引擎; - 'skyline': Skyline 渲染引擎; - 'xr-frame': xr-frame 解决方案; | ||||||||
| |||||||||||
| webviewId | number | 否 | 当前页面 id | ||||||||
onAppRoute 只描述最终实际生效的路由。一次路由发生重写时,被替换的中间目标不会触发 onAppRoute,只有最终成功进入的页面触发一次。
监听器抛出的异常不会中断底层路由流程,但应在监听器内部妥善处理业务异常。
const appRouteCallback = (event : AppRouteEvent) => {
console.log(`路由完成:${event.openType} ${event.path}`)
console.log(`路由参数:${JSON.stringify(event.query)}`)
}
uni.onAppRoute(appRouteCallback)
// 不再监听时,传入注册时的同一个函数对象。
uni.offAppRoute(appRouteCallback)
取消监听应用路由事件。不传 callback 时移除全部监听器。
| Web | 微信小程序 | Android | iOS | HarmonyOS |
|---|---|---|---|---|
| 5.25 | 5.25 | 5.25 | 5.25 | 5.25 |
| 名称 | 类型 | 必填 | 描述 |
|---|---|---|---|
| callback | (event: AppRouteEvent) => void | 否 | 应用路由事件回调 |
| 名称 | 类型 | 必备 | 兼容性 | 描述 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| path | string | 是 | 路由页面路径,不包含开头的斜杠 | ||||||||
| query | UTSJSONObject | 是 | 路由页面参数 | ||||||||
| openType | string | 是 | 应用路由类型。 | ||||||||
| |||||||||||
| notFound | boolean | 是 | 路由页面是否不存在。支付宝小程序不提供该信息,固定为 false | ||||||||
| timeStamp | number | 是 | 事件触发时的时间戳 | ||||||||
| routeEventId | string | 是 | 路由事件唯一标识 | ||||||||
| page | IAnyObject | 否 | 当前打开页面的相关配置 | ||||||||
| pipMode | string | 否 | 可选值: - 'min': 视频页面缩小为小窗; - 'max': 视频小窗还原为页面; | ||||||||
| |||||||||||
| renderer | string | 否 | 渲染引擎 可选值: - 'webview': Webview 渲染引擎; - 'skyline': Skyline 渲染引擎; - 'xr-frame': xr-frame 解决方案; | ||||||||
| |||||||||||
| webviewId | number | 否 | 当前页面 id | ||||||||
传入监听函数时,只移除同一个函数对象对应的监听器;不传参数或传入 null 时,移除通过 onAppRoute 注册的全部监听器。
const callback1 = (event : AppRouteEvent) => {
console.log(event.path)
}
const callback2 = (event : AppRouteEvent) => {
console.log(event.openType)
}
uni.onAppRoute(callback1)
uni.onAppRoute(callback2)
// 只移除 callback1。
uni.offAppRoute(callback1)
// 移除全部 onAppRoute 监听器。
uni.offAppRoute()
onAppRoute 和 onBeforeAppRoute 的监听器相互独立,清空其中一类不会影响另一类。
监听应用路由发生前的事件。
| Web | 微信小程序 | Android | iOS | HarmonyOS |
|---|---|---|---|---|
| 5.25 | 5.25 | 5.25 | 5.25 | 5.25 |
| 名称 | 类型 | 必填 | 描述 |
|---|---|---|---|
| callback | (event: BeforeAppRouteEvent) => void | 是 | 应用路由前置事件回调 |
| 名称 | 类型 | 必备 | 兼容性 | 描述 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| path | string | 是 | 路由页面路径,不包含开头的斜杠 | ||||||||
| query | UTSJSONObject | 是 | 路由页面参数 | ||||||||
| openType | string | 是 | 应用路由类型。 | ||||||||
| |||||||||||
| notFound | boolean | 是 | 路由页面是否不存在。支付宝小程序不提供该信息,固定为 false | ||||||||
| routeEventId | string | 是 | 路由事件唯一标识 | ||||||||
| page | IAnyObject | 否 | 当前打开页面的相关配置 | ||||||||
| pipMode | string | 否 | 可选值: - 'min': 视频页面缩小为小窗; - 'max': 视频小窗还原为页面; | ||||||||
| |||||||||||
| renderer | string | 否 | 渲染引擎 可选值: - 'webview': Webview 渲染引擎; - 'skyline': Skyline 渲染引擎; - 'xr-frame': xr-frame 解决方案; | ||||||||
| |||||||||||
| webviewId | number | 否 | 当前页面 id | ||||||||
onBeforeAppRoute 回调同步执行。可以根据目标页面、路由参数和路由类型记录信息,也可以在该回调中同步调用 rewriteRoute。
const beforeAppRouteCallback = (event : BeforeAppRouteEvent) => {
console.log(`准备路由:${event.openType} ${event.path}`)
console.log(`路由参数:${JSON.stringify(event.query)}`)
}
// 如需处理 appLaunch,应在 App.uvue 的 onLaunch 中尽早注册。
onLaunch(() => {
uni.onBeforeAppRoute(beforeAppRouteCallback)
})
同一个 routeEventId 对应的 onBeforeAppRoute 最多触发一次。重写后的目标会作为新一轮路由再次触发 onBeforeAppRoute,并使用新的 routeEventId。
取消监听应用路由前置事件。不传 callback 时移除全部监听器。
| Web | 微信小程序 | Android | iOS | HarmonyOS |
|---|---|---|---|---|
| 5.25 | 5.25 | 5.25 | 5.25 | 5.25 |
| 名称 | 类型 | 必填 | 描述 |
|---|---|---|---|
| callback | (event: BeforeAppRouteEvent) => void | 否 | 应用路由前置事件回调 |
| 名称 | 类型 | 必备 | 兼容性 | 描述 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| path | string | 是 | 路由页面路径,不包含开头的斜杠 | ||||||||
| query | UTSJSONObject | 是 | 路由页面参数 | ||||||||
| openType | string | 是 | 应用路由类型。 | ||||||||
| |||||||||||
| notFound | boolean | 是 | 路由页面是否不存在。支付宝小程序不提供该信息,固定为 false | ||||||||
| routeEventId | string | 是 | 路由事件唯一标识 | ||||||||
| page | IAnyObject | 否 | 当前打开页面的相关配置 | ||||||||
| pipMode | string | 否 | 可选值: - 'min': 视频页面缩小为小窗; - 'max': 视频小窗还原为页面; | ||||||||
| |||||||||||
| renderer | string | 否 | 渲染引擎 可选值: - 'webview': Webview 渲染引擎; - 'skyline': Skyline 渲染引擎; - 'xr-frame': xr-frame 解决方案; | ||||||||
| |||||||||||
| webviewId | number | 否 | 当前页面 id | ||||||||
offBeforeAppRoute 的移除规则与 offAppRoute 相同:传入注册时的同一个函数对象,只移除该监听器;不传参数或传入 null,移除全部前置路由监听器。
const beforeAppRouteCallback = (event : BeforeAppRouteEvent) => {
console.log(event.path)
}
uni.onBeforeAppRoute(beforeAppRouteCallback)
uni.offBeforeAppRoute(beforeAppRouteCallback)
// 移除全部 onBeforeAppRoute 监听器。
uni.offBeforeAppRoute()
在应用路由前置事件回调中重写当前路由。
| Web | 微信小程序 | Android | iOS | HarmonyOS |
|---|---|---|---|---|
| 5.25 | 5.25 | 5.25 | 5.25 | 5.25 |
| 名称 | 类型 | 必填 | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| options | RewriteRouteOptions | 是 | ||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
| 名称 | 类型 | 必备 |
|---|---|---|
| errMsg | string | 是 |
| 名称 | 类型 | 必备 | 描述 |
|---|---|---|---|
| errCode | number | 是 | 路由错误码 - 4: 框架内部异常 |
| errSubject | string | 是 | 统一错误主题(模块)名称 |
| data | any | 否 | 错误信息中包含的数据 |
| cause | Error | 否 | 源错误信息,可以包含多个错误,详见SourceError |
| errMsg | string | 是 |
| 名称 | 类型 | 必备 |
|---|---|---|
| errMsg | string | 是 |
rewriteRoute 用于重写当前正在处理的路由事件,且不支持 Promise 风格调用。调用时需遵守以下规则:
onBeforeAppRoute 回调中同步调用。在回调外调用,或在回调中的异步任务内调用,都会失败。routeEventId 只允许成功重写一次。存在多个前置监听器时,首次重写成功后,同一轮的后续重写调用会失败。openType。navigateBack 路由不允许重写。switchTab 只能重写到 tabBar 页面,navigateTo 不能重写到 tabBar 页面。fail 返回失败信息。preserveQuery 默认为 false:
false 时,使用 url 中携带的参数。true 时,完整保留当前路由事件的参数,并丢弃 url 中携带的参数。例如,当前目标参数为 a=1,重写地址为 /pages/new/new?b=2。preserveQuery 为 false 时最终参数为 b=2;为 true 时最终参数为 a=1。
const beforeAppRouteCallback = (event : BeforeAppRouteEvent) => {
if (
event.openType == 'navigateTo' &&
event.path == 'pages/old/old'
) {
uni.rewriteRoute({
url: '/pages/new/new?from=rewrite',
preserveQuery: false,
success: (result) => {
console.log(result.errMsg)
},
fail: (error) => {
console.error(error.errMsg)
},
complete: (result) => {
console.log(result.errMsg)
}
})
}
}
uni.onBeforeAppRoute(beforeAppRouteCallback)
success 表示本次重写请求已被接受;fail 表示本次重写被拒绝;无论成功或失败都会调用 complete。
重写后的目标会作为新的路由事件重新触发 onBeforeAppRoute。因此,“同一个路由事件只允许成功重写一次”不表示一次用户跳转只能重写一次。
A -> onBeforeAppRoute(routeEventId=1) -> 重写到 B
B -> onBeforeAppRoute(routeEventId=2) -> 重写到 C
C -> onBeforeAppRoute(routeEventId=3) -> 执行路由
C -> onAppRoute(routeEventId=3)
业务代码应避免不同重写规则之间形成循环。
完整示例代码参考 hello uni-app x 应用路由事件示例。
示例为hello uni-app x alpha分支,与最新HBuilderX Alpha版同步。与最新正式版同步的master分支示例另见
示例
<template>
<view class="route-page uni-theme-root">
<page-head title="应用路由事件"></page-head>
<view class="uni-padding-wrap">
<view class="uni-list-cell-padding status-box">
<text class="uni-title-text">监听状态</text>
<text class="status-text">{{ data.isListening ? '监听中' : '已停止' }}</text>
<text class="status-text">onBeforeAppRoute:{{ data.beforeAppRouteCount }} 次</text>
<text class="status-text">onAppRoute:{{ data.appRouteCount }} 次</text>
</view>
<view class="uni-btn-v uni-common-mt">
<button type="primary" @click="navigateToTarget">普通跳转</button>
<button @click="navigateToRewriteTarget">重写下一次跳转</button>
<button @click="startListen">开始监听</button>
<button @click="stopListen">停止监听</button>
<button @click="clearRecords">清空记录</button>
</view>
<view class="event-box uni-common-mt">
<text class="uni-title-text">onBeforeAppRoute 记录</text>
<text v-if="data.beforeAppRouteEvents.length == 0" class="event-text">暂无记录</text>
<view v-for="(event, index) in data.beforeAppRouteEvents" :key="index" class="event-item">
<text class="event-index">#{{ index + 1 }}</text>
<text class="event-text">{{ event }}</text>
</view>
</view>
<view class="event-box uni-common-mt">
<text class="uni-title-text">onAppRoute 记录</text>
<text v-if="data.appRouteEvents.length == 0" class="event-text">暂无记录</text>
<view v-for="(event, index) in data.appRouteEvents" :key="index" class="event-item">
<text class="event-index">#{{ index + 1 }}</text>
<text class="event-text">{{ event }}</text>
</view>
</view>
<view class="event-box uni-common-mt">
<text class="uni-title-text">最近一次 rewriteRoute 结果</text>
<text class="event-text">{{ data.rewriteRouteResult }}</text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
const TARGET_PATH = 'pages/API/app-route/app-route-target'
const TARGET_URL = `/${TARGET_PATH}`
type DataType = {
isListening : boolean
beforeAppRouteCount : number
appRouteCount : number
beforeAppRouteEvents : string[]
appRouteEvents : string[]
lastNavigateToBeforePath : string
lastNavigateToAppRoutePath : string
rewriteRouteResult : string
}
const data = reactive({
isListening: false,
beforeAppRouteCount: 0,
appRouteCount: 0,
beforeAppRouteEvents: [] as string[],
appRouteEvents: [] as string[],
lastNavigateToBeforePath: '',
lastNavigateToAppRoutePath: '',
rewriteRouteResult: ''
} as DataType)
let rewriteNextRoute = false
const appRouteCallback = (event : AppRouteEvent) => {
data.appRouteCount++
data.appRouteEvents.push(JSON.stringify(event))
if (event.openType == 'navigateTo') {
data.lastNavigateToAppRoutePath = event.path
}
}
const beforeAppRouteCallback = (event : BeforeAppRouteEvent) => {
data.beforeAppRouteCount++
data.beforeAppRouteEvents.push(JSON.stringify(event))
if (event.openType == 'navigateTo') {
data.lastNavigateToBeforePath = event.path
}
if (rewriteNextRoute && event.openType == 'navigateTo' && event.path == TARGET_PATH) {
rewriteNextRoute = false
uni.rewriteRoute({
url: `${TARGET_URL}?from=rewrite`,
success: (result) => {
data.rewriteRouteResult = result.errMsg
},
fail: (error) => {
data.rewriteRouteResult = error.errMsg
}
})
}
}
const startListen = () => {
if (data.isListening) {
return
}
uni.onAppRoute(appRouteCallback)
uni.onBeforeAppRoute(beforeAppRouteCallback)
data.isListening = true
}
const stopListen = () => {
if (!data.isListening) {
return
}
uni.offAppRoute(appRouteCallback)
uni.offBeforeAppRoute(beforeAppRouteCallback)
data.isListening = false
rewriteNextRoute = false
}
const clearRecords = () => {
data.beforeAppRouteCount = 0
data.appRouteCount = 0
data.beforeAppRouteEvents.length = 0
data.appRouteEvents.length = 0
data.lastNavigateToBeforePath = ''
data.lastNavigateToAppRoutePath = ''
data.rewriteRouteResult = ''
}
const navigateToTarget = () => {
rewriteNextRoute = false
uni.navigateTo({
url: `${TARGET_URL}?from=normal`
})
}
const enableRewriteNextRoute = () => {
rewriteNextRoute = true
}
const navigateToRewriteTarget = () => {
enableRewriteNextRoute()
uni.navigateTo({
url: `${TARGET_URL}?from=source`,
fail: () => {
rewriteNextRoute = false
}
})
}
onLoad(() => {
startListen()
})
onUnload(() => {
stopListen()
})
defineExpose({
data,
startListen,
stopListen,
clearRecords,
enableRewriteNextRoute,
navigateToTarget,
navigateToRewriteTarget
})
</script>
<style>
.status-box,
.event-box {
padding: 12px;
background-color: var(--list-background-color, #ffffff);
}
.status-text,
.event-text {
margin-top: 8px;
color: var(--text-color, #333333);
}
.event-text {
width: 100%;
}
.event-item {
padding-top: 8px;
padding-bottom: 8px;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border-color, #eeeeee);
}
.event-index {
color: var(--active-color, #999999);
}
</style>
| 名称 | 类型 | 必备 | 描述 |
|---|---|---|---|
| errMsg | string | 是 | 错误信息 |