路由器钩子

路由导航的总体流程如下图所示:

image 2023 10 14 21 19 47 581
Figure 1. Figure 6.23: Navigation resolution flow diagram

一旦在某个路由上触发了导航,Vue 路由器就会为开发人员提供几个主要的导航保护或钩子,以保护或拦截导航过程。根据类型的不同,这些保护可以在全局或组件中挂钩。下面是一些示例:

  • 全局:beforeEach、beforeResolve 和 afterEach

  • 每个组件:beforeEnter

  • 组件内:beforeRouteUpdate、beforeRouteEnter 和 beforeRouterLeave

如图 6.23 所示,只有在所有 Hook 或防护(包括任何异步防护)都已解析(resolved)后,导航才被视为完成。 现在,让我们看看如何设置 beforeEach Hooks。

设置 beforeEach 钩子

beforeEach 是一个全局 Hook,在导航开始时调用,在触发其它全局和组件内 Hook 之前(前一个视图组件的 beforeRouteLeave 除外)。它应该在初始化期间在 index.js 文件中定义为路由器实例的全局方法,并采用以下语法:

const router = new VueRouter({
//...
})

router.beforeEach(beforeEachCallback)

在前面的代码片段中,beforeEachCallback 是一个接收三个参数的钩子函数:

const beforeEachCallback = (
    to, // The destination route
    from, //The source route
    next //The function to trigger to resolve the hook
) => { … })

或者我们可以直接这样写:

router.beforeEach((to, from, next) => { … })

例如,如果我们想要在用户导航到 About 而没有用户参数值时显示通用消息(有参数时,会根据参数显示不同的页面),我们可以如下挂钩 beforeEach :

router.beforeEach((
    to, // The destination route
    from, //The source route
    next //The function to trigger to resolve the hook
) => {
    if (to.name === 'about' && (!to.params || !to.params.user)) {
        next({ name: 'error' })
    } else {
        next();
    }
})

在这里,我们检查目标路由是否是 about,如果它没有传递任何附加参数,也没有传递任何用户参数值,我们将导航到错误(error)路由。否则,只需正常执行 next() 即可。

next() 需要在任何给定的非重叠流逻辑中精确调用一次(一次用于 if,一次用于 else),否则会出现错误。

我们仍然需要使用 Error.vue 视图组件创建一个错误(error)页面,显示一条简单的消息:

<template>
    <div>
        <h2>No param passed.</h2>
    </div>
</template>

另外,请确保相应地注册路径:

{
    path: '/error',
    name: 'error',
    component: () => import(/* webpackChunkName: "error" */'../views/Error.vue'),
}

现在,在主页(Home)视图中,单击 About 链接后,应用程序将渲染 Error 页面,而不是 About 页面,如以下屏幕截图所示:

image 2023 10 14 21 54 02 362
Figure 2. Figure 6.24: Error page displayed when About is clicked without any param passed

现在,让我们转到 App.vue 文件并指定 to 属性来绑定到 { name: 'about', params: { user: 'Adam' }} 对象:

<router-link :to="{ name: 'about', params: { user: 'Adam'}}">About</router-link>

让我们导航回到应用程序的主页(Home)并单击 About 链接。由于我们传递了正确的参数,因此输出将如下所示:

image 2023 10 14 21 56 21 025
Figure 3. Figure 6.25: About page displayed when there is a user passed in the params

此外,从现在开始,每次刷新 About 页面时,我们都会被重定向到 Error 页面,因为刷新时没有传递用户(user)参数。

现在我们将看看 beforeEach 和 beforeResolve Hook 之间的几个关键区别点。

区分 beforeEach 和 beforeResolve 钩子

我们还可以使用相同的语法向 beforeResolve 注册全局 Hook。 然而,与在导航创建阶段触发的 beforeEach 不同,beforeResolve 将在所有 Hooks(全局的和组件内的)解析之后,在导航执行并确认之前触发:

router.beforeResolve((
    to, // The destination route
    from, //The source route
    next //The function to trigger to resolve the hook
) => {
    if (to.name === 'about' && (!to.params || !to.params.user))
    {
        next({ name: 'error' })
    } else {
        next();
    }
})

输出结果将保持与图 6.25 相同:

image 2023 10 14 22 03 39 181
Figure 4. Figure 6.26: About page displayed when there is a user passed in the params

现在让我们详细看看 afterEach Hook。

afterEach 钩子

afterEach() Hook 是确认导航后(即 beforeResolve() 之后)触发的最后一个全局导航守卫。 与其它全局防护不同,传递给 afterEach() 的 Hook 函数不会接收 next 函数,因此它不会影响导航。

此外,to 和 from 参数是只读 Route 对象。因此,afterEach 的最佳用例是保存数据,例如后退按钮的上次访问的 Route 对象、传递的路由目的地参数或页面视图跟踪。例如,我们可以有一个默认值 user,分配它,并在需要时保存它:

let user = 'Adam';

router.beforeEach((to, from, next) => {
    if (to.name === 'about' && (!to.params || !to.params.user)) {
        next({ name: 'about', params: { user }})
    } else {
        user = to.params.user;
        next()
    }
});
router.afterEach((to, from) => {
    if (to.name === 'about' && to.params && to.params.user) {
        user = to.params.user;
    }
})

现在,在 App.js 文件中,而不是 Adam,添加以下内容:

<router-link
    :to="{ name: 'about', params: { user: 'Adam' }}"
>
About
</router-link>

让我们将其更改为 Alex:

<router-link
    :to="{ name: 'about', params: { user: 'Alex' }}"
>
About
</router-link>

现在单击 About 链接时的输出如下:

image 2023 10 14 22 13 03 192
Figure 5. Figure 6.27: About page displaying the new user’s name – Alex

但在重新加载时,About 页面会使用默认用户 Adam 渲染,因为有一个用户传递给了参数,如下所示:

image 2023 10 14 22 14 52 832
Figure 6. Figure 6.28: About page displaying the default user value on reload – Adam

在本节中,我们研究了 afterEach Hook。我们使用 afterEach Hook 将数据传递到 about 页面,而不必将该数据包含在 URL 中。同样的技术可用于更新其它行为,例如按下后退按钮时所需的目标页面。

个性化每个路由的钩子

我们可以直接在目标路由的配置对象中定义一个 beforeEnter 防护,而不是定义一个全局 Hook(这可能会导致看不见的错误并需要进行路由检查),例如我们的 About 路由:

beforeEnter: (to, from, next) => {
    if (!to.params || !to.params.user) {
        to.params.user = 'Adam'
    }
    next()
}

通过这种方法,无论是重新加载还是单击链接导航到 About 页面,输出现在都是一致的,如以下屏幕截图所示:

image 2023 10 14 22 17 45 784
Figure 7. Figure 6.29: About page rendered with the user value Adam

使用 beforeEnter(),to 是可写的,您将可以访问它(它指向特定的路由 - About)。 仅当用户触发导航到 About 页面时才会触发它。

在本节中,我们研究了 Vue 中可用的不同路由器 Hook,包括 beforeEach、beforeResolve 和 afterEach。 我们看到了如何在路由过程的不同点调用每个 Hook。 作为一个实际示例,我们研究了一条路由,如果未提供参数,该路由会将用户引导至错误页面。这些 Hook 非常有用,尤其是在设置经过身份验证的路由时。在下一节中,我们将了解如何设置组件内 Hook。