- 开始时间:2020-03-26
- 目标主要版本:Vue Router v4
- 引用 issue:https://github.com/vuejs/vue-router/issues/2833, https://github.com/vuejs/vue-router/pull/3047, https://github.com/vuejs/vue-router/issues/2932, https://github.com/vuejs/vue-router/issues/2881
- 实现的 PR:N/A
摘要
- 明确定义什么是导航失败,以及我们如何和在哪里捕获它们。
- 改变基于 Promise 的
router.push(和router.replace的扩展)何时 resolve 和 reject。 - 使
router.push与router.afterEach和router.onError一致。
基本范例
router.push
如果有一个未处理的错误或一个错误被传递到 next:
// any other navigation guardrouter.beforeEach((to, from, next) => {next(new Error())// orthrow new Error()// orreturn Promise.reject(new Error())})
那么 router.push 返回的 promise 是在 reject 状态的:
router.push('/url').catch((err) => {// ...})
在所有其他请下,promise 是在 resolve 状态。我们可以通过检查 resolve 后的值知道导航是否失败:
router.push('/dashboard').then((failure) => {if (failure) {failure instanceof Error // truefailure.type // NavigationFailure.canceled}})
router.afterEach
这是全局性的,相当于 router.push().then()。
router.onError
这也是全局性的,相当于 router.push().catch()。
动机
目前 Vue Router 关于 push 返回 promise 的行为与 router.afterEach 和 router.onError 不一致。理想情况下,我们应该能够在全局和本地捕获所有成功和失败的导航,但我们只能在本地做到这一点。
onError只在抛出的错误和next(new Error())中才会触发afterEach只有在导航时才会被调用- 当涉及到
router.push和router.afterEach/router.onError的调用时,重定向的行为应该与导航守卫中的next("/url")相同。唯一的区别是,redirect只会触发被重定向位置的 leave 守卫和其他之前的守卫,而不是原来的位置。
Promise 的 resolve/reject 和 router.afterEach 和 router.onError 不一致并且令人困惑。
具体设计
其中一个要点是要能够在全局和本地一致的处理失败的导航:
- 失败的导航:
- 触发
router.afterEach router.push返回 resolve 状态的Promise。
- 触发
- 未捕获的错误,next(new Error())
- 触发
router.onError router.push返回 reject 状态的Promise。
- 触发
需要注意的是,这两组没有重叠:如果导航守卫中存在未处理的错误,它将触发 router.onError 以及由 router.push 返回 reject 状态的 Promise,但不会触发 router.afterEach。用 next(false) 取消一个导航不会触发 router.onError,但会触发 router.afterEach。
Promise resolve 和 reject 的更改
像 push 这样的导航方法返回一个 Promise,一旦导航成功或失败,这个 promise 就会变为 resolve。只有在出现未处理的错误时,它才会变为 reject。如果它变为 reject,它也将触发 router.onError。
为了区分成功的导航和失败的导航,push 返回的 Promise 将 resolve 一个 undefined 或 NavigationFailure。
import { NavigationFailureType, isNavigationFailure } from 'vue-router'router.push('/').then((failure) => {if (failure) {// Having an Error instance allows us to have a Stacktrace and trace back// where the navigation was cancelled. This will, in many cases, lead to a// Navigation Guard and the corresponding `next()` call that cancelled the// navigationfailure instanceof Error // trueif (isNavigationFailure(failure, NavigationFailureType.canceled)) {// ...}}})// using async/awaitlet failure = await router.push('/')if (failure) {// ...}
通过在导航失败时 Promise 不是 reject,我们避免了未捕获(in promise)的错误,同时仍然保持了检查导航是否失败的可能性。
导航失败
有几个不同的导航错误,能够在你的代码中做出不同的反应
导航失败可能通过一个 type 属性来区分,尽管你不需要直接检查它。所有可能的值都由一个 menu 来保持,即 NavigationFailure:
aborted:当前导航正在进行时,发生了一个新的导航。cancelled:在一个导航守卫中执行了 next(false)。duplicated:导航到与当前导航相同的位置将取消导航,并且不调用任何导航守卫。
在 type 属性的基础上,导航失败也暴露了 from 和 to 属性,与 router.afterEach 一样。
重定向
用 next('/url') 在一个导航守卫中重定向本身并不是一个导航失败,因为导航仍然在进行,并在某个地方结束。为了检测导航守卫,特别是在 SSR 期间,在 currentRoute 上有一个 redirectedFrom 属性可以访问。
例如:想象一下,当用户没有经过认证时,一个导航守卫会重定向到 /login:
router.beforeEach((to, from, next) => {// redirect to the login page if the target location requires authenticationif (to.meta.requiresAuth && !isAuthenticated) next('/login')else next()})
当导航到一个需要认证的位置时,我们可以通过 redirectedFrom 检索到用户试图访问原始位置:
// user is not authenticatedawait router.push('/profile/dashboard')// `redirectedFrom` is a RouteLocationNormalized, like `currentRoute` but we are omitting// most properties to make the example readablerouter.currentRoute // { path: '/login', redirectedFrom: { path: '/profile/dashboard' } }
router.afterEach 的更改
由于 router.afterEach 在导航失败时也会被触发,我们需要一种方法来知道导航是成功还是失败。为了做到这一点,我们引入了一个额外的参数,其中包含了我们可以在已 resolved 的导航中找到的同样的导航失败:
import { NavigationFailureType, isNavigationFailure } from 'vue-router'router.afterEach((to, from, failure) => {if (isNavigationFailure(failure)) {// ...}})
区分导航失败的情况
我们可以使用 isNavigationFailure helper,而不是直接检查 type 属性:
import { NavigationFailureType, isNavigationFailure } from 'vue-router'router.afterEach((to, from, failure) => {// Any kind of navigation failureif (isNavigationFailure(failure)) {// ...}// Only duplicated navigationsif (isNavigationFailure(failure, NavigationFailureType.duplicated)) {// ...}// Aborted or canceled navigationsif (isNavigationFailure(failure,NavigationFailureType.aborted | NavigationFailureType.canceled)) {// ...}})
缺点
- 破坏性变化虽然迁移相对简单,在许多情况下,将允许开发人员删除现有代码。
备选方案
N/A
采纳策略
- 在 vue-router@3 中暴露
NavigationFailureType和isNavigationFailure,这样就可以将导航错误和普通的错误区分开来。 afterEach和onError在导航中失败时不会 reject。任何依赖捕获错误的代码都应该 await promise 的结果。
没有解决的问题
N/A
