Vue Router 的 onError
和 onReady
:路由世界的两大“守护神”
大家好,我是老码,今天咱们来聊聊 Vue Router 里两个不太起眼,但关键时刻能救命的钩子:onError
和 onReady
。它们就像路由世界里的两大“守护神”,一个负责处理突发状况,一个确保一切准备就绪。
在深入源码之前,先明确一下:我们讨论的是 Vue Router 3.x 版本,因为 Vue Router 4.x 做了不少改动,这两个钩子的用法和内部实现有所不同。
onError
:错误处理的急先锋
先来说说 onError
。顾名思义,这个钩子就是在路由导航过程中出现错误时被调用的。想象一下,你辛辛苦苦配置了一堆路由,结果用户访问了一个不存在的路径,或者某个组件加载失败了,这时候,onError
就派上用场了。
作用:
- 捕获并处理路由导航过程中的错误。
- 提供了一种全局性的错误处理机制,避免错误直接抛给用户。
- 方便开发者进行错误日志记录、错误上报等操作。
何时触发:
onError
在以下情况下会被触发:
- 路由匹配失败(比如访问了一个未定义的路径)。
- 路由守卫(
beforeEach
、beforeResolve
、afterEach
)中发生了错误。 - 组件的
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
钩子中发生了错误。 - 异步组件加载失败。
源码分析:
在 Vue Router 的源码中,onError
的调用主要发生在 matcher.match
、路由守卫执行、组件钩子执行等可能出错的地方。
// 简化版,仅展示关键逻辑
function runQueue (queue, iterator, cb) {
// ... 队列执行逻辑 ...
function step (index) {
if (index >= queue.length) {
cb()
} else {
if (route) {
try { // 注意这里的 try...catch
iterator(route, from, next)
} catch (e) {
// 捕获错误,并调用 onError 钩子
router.onError && router.onError(e)
cb(e) // 传递错误给后续流程
}
} else {
// ...
}
}
}
step(0)
}
VueRouter.prototype.push = function push (location, onComplete, onAbort) {
// ...
let route = this.match(location, current) // 可能出错
// ...
runQueue(
// ... 路由守卫队列 ...
(route, redirect, next) => {
// ...
try {
// 执行路由守卫,可能出错
redirect(route)
} catch (err) {
// 捕获错误,并调用 onError 钩子
this.onError && this.onError(err)
if (onAbort) {
onAbort(err)
}
}
},
err => {
// ...
}
)
// ...
}
可以看到,Vue Router 在关键流程中使用了 try...catch
语句来捕获错误,并在 catch
块中调用了 router.onError
。
用法示例:
const router = new VueRouter({
routes: [
// ...
]
})
router.onError((error) => {
console.error('路由导航发生错误:', error)
// 可以进行错误上报、日志记录等操作
// 比如:
// reportErrorToServer(error)
// logError(error)
})
注意事项:
onError
钩子是全局性的,会捕获所有路由导航过程中的错误。- 如果
onError
钩子本身也发生了错误,Vue Router 不会再次调用onError
,以避免无限循环。 onError
只是提供了一种错误处理机制,并不能阻止错误的发生。开发者仍然需要仔细检查代码,避免出现错误。
onReady
:万事俱备,只欠东风
再来说说 onReady
。这个钩子是在 Vue Router 完成首次导航(initial navigation)时被调用的。也就是说,当你的 Vue 应用完成首次渲染,并且路由已经完成了初始化的配置,onReady
就会被触发。
作用:
- 确保路由已经初始化完成,可以安全地执行一些依赖于路由的操作。
- 用于处理一些需要在首次导航完成后才能执行的任务。
- 例如,获取初始数据、启动某些服务等。
何时触发:
onReady
在以下情况下会被触发:
- 首次导航完成,并且 Vue Router 已经完成了内部的初始化工作。
- 如果 Vue Router 已经初始化完成,再次调用
onReady
会立即执行回调函数。
源码分析:
onReady
的实现比较巧妙,它利用了一个内部的 ready
标志位和一个队列来管理回调函数。
// 简化版,仅展示关键逻辑
VueRouter.prototype.onReady = function onReady (cb, errorCb) {
this.app ? cb() : this.afterHooks.push(() => {
cb()
})
if (errorCb) {
this.errorCbs.push(errorCb)
}
return this
}
VueRouter.prototype.init = function init (app /* Vue component instance */) {
// ...
this.app = app
// ...
const router = this
Vue.util.defineReactive(this, 'currentRoute', this.history.current)
this.history.listen(route => {
this.app._route = route
})
this.afterHooks.forEach(hook => {
hook.call(null, this.app)
})
this.afterHooks = []
// ...
}
当 VueRouter
实例初始化时,ready
标志位为 false
。当首次导航完成后,ready
标志位被设置为 true
,并且会执行所有注册在 afterHooks
队列中的回调函数。
用法示例:
const router = new VueRouter({
routes: [
// ...
]
})
router.onReady(() => {
console.log('Vue Router 初始化完成!')
// 可以执行一些需要在路由初始化完成后才能执行的任务
// 比如:
// fetchInitialData()
// startService()
}, (err) => {
console.log('Vue Router 初始化失败!', err)
})
注意事项:
onReady
钩子只会执行一次,即在首次导航完成后。- 如果 Vue Router 已经初始化完成,再次调用
onReady
会立即执行回调函数。 onReady
钩子可以接收一个可选的错误回调函数,用于处理初始化失败的情况。
onError
和 onReady
在路由生命周期中的位置
为了更清晰地理解 onError
和 onReady
在路由生命周期中的位置,我们来看一张简化的流程图:
+---------------------+
| 创建 VueRouter |
+---------------------+
|
v
+---------------------+
| 注册路由配置 |
+---------------------+
|
v
+---------------------+
| 初始化 Vue 应用 |
+---------------------+
|
v
+---------------------+
| 首次导航 (Initial) |
+---------------------+
|
v
+---------------------+
| 路由守卫执行 | (beforeEach, beforeResolve, afterEach)
+---------------------+
| (如果发生错误)
v
+---------------------+
| onError 钩子 |
+---------------------+
|
v
+---------------------+
| 组件钩子执行 | (beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave)
+---------------------+
| (如果发生错误)
v
+---------------------+
| onError 钩子 |
+---------------------+
|
v
+---------------------+
| 导航完成 |
+---------------------+
|
v
+---------------------+
| onReady 钩子 |
+---------------------+
|
v
+---------------------+
| 后续导航 (Subsequent) |
+---------------------+
表格总结:
钩子 | 作用 | 触发时机 | 生命周期位置 |
---|---|---|---|
onError |
捕获并处理路由导航过程中的错误。 | 路由匹配失败、路由守卫发生错误、组件钩子发生错误、异步组件加载失败等。 | 在路由守卫、组件钩子等执行过程中发生错误时立即触发。 |
onReady |
确保路由已经初始化完成,可以安全地执行一些依赖于路由的操作。 | 首次导航完成,并且 Vue Router 已经完成了内部的初始化工作。 | 在首次导航完成后触发,用于处理需要在路由初始化完成后才能执行的任务。 |
进阶使用:结合 Promise
和 async/await
在现代 JavaScript 开发中,Promise
和 async/await
已经成为处理异步操作的标配。我们可以将 onError
和 onReady
钩子与 Promise
结合使用,以更优雅地处理异步逻辑。
onError
结合 Promise
:
const router = new VueRouter({
routes: [
// ...
]
})
function handleError (error) {
console.error('路由导航发生错误:', error)
// 可以进行错误上报、日志记录等操作
return Promise.reject(error) // 将错误传递给 Promise 链
}
router.beforeEach((to, from, next) => {
// 模拟一个可能出错的异步操作
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
reject(new Error('模拟异步操作失败'))
} else {
resolve()
}
}, 500)
})
.then(() => {
next()
})
.catch(err => {
handleError(err)
next(false) // 中断路由导航
})
})
router.onError(handleError)
onReady
结合 async/await
:
const router = new VueRouter({
routes: [
// ...
]
})
async function initApp () {
return new Promise((resolve, reject) => {
router.onReady(resolve, reject)
})
}
(async () => {
try {
await initApp()
console.log('Vue Router 初始化完成!')
// 可以执行一些需要在路由初始化完成后才能执行的任务
// 比如:
// await fetchInitialData()
// await startService()
} catch (err) {
console.error('Vue Router 初始化失败!', err)
}
})()
总结
onError
和 onReady
是 Vue Router 中两个非常实用的钩子,它们分别负责处理错误和确保路由初始化完成。合理地使用这两个钩子,可以使你的 Vue 应用更加健壮和可靠。
记住,onError
是错误处理的急先锋,onReady
是万事俱备的信号。掌握它们,你就能更好地掌控你的 Vue 路由,让你的应用在各种情况下都能稳定运行。
今天的分享就到这里,希望对大家有所帮助。 祝大家编程愉快!