使用 Vue Router 编写中间件
在学习中间件在 Nuxt
应用中如何工作之前,我们应该先了解它在标准的 Vue
应用中是如何工作的。此外,在 Vue
应用中创建中间件之前,让我们首先了解它们是什么。
什么是中间件?
简单来说,中间件是位于两个或多个软件之间的软件层。这是软件开发中的一个古老概念。中间件一词自 1968 年开始使用,并在 20 世纪 80 年代作为将较新的应用程序连接到较旧的遗留系统问题的解决方案而流行起来。关于它的定义有很多,例如(来自 Google 词典)“[中间件是]充当操作系统或数据库与应用程序之间桥梁的软件,尤其是在网络上。”
在 Web
开发领域,服务器端软件或应用程序(如 Koa
和 Express
)接收请求并输出响应。中间件是在传入请求之后执行的程序或函数,它们产生可能是最终输出或供下一个中间件使用的输出,直到周期完成。这也意味着我们可以有多个中间件,它们将按照声明的顺序执行。

此外,中间件不仅限于服务器端技术。当你的应用程序中有路由时,它在客户端也非常常见。Vue.js
的 Vue Router
就是使用这种中间件概念的一个很好的例子。我们在第四章 “添加视图、路由和过渡” 中已经学习并使用了 Vue Router
,为我们的 Vue
应用程序创建路由。现在,让我们更深入地研究 Vue Router
的高级用法——导航守卫。
安装 Vue Router
如果你从一开始就学习本书的章节,你应该已经从第四章 “添加视图、路由和过渡” 中了解如何安装 Vue Router
。然而,这里有一个快速回顾。
按照以下步骤直接下载 Vue Router
:
-
点击以下链接并下载源代码: https://unpkg.com/vue-router/dist/vue-router.js
-
在
Vue
之后包含router
,以便它可以自动安装:<script src="/path/to/vue.js"></script> <script src="/path/to/vue-router.js"></script>
或者,你可以通过 npm
安装 Vue Router
:
-
使用
npm
将router
安装到你的项目中:$ npm i vue-router
-
使用
use
方法显式注册router
:import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
-
一旦你安装了
router
,你就可以开始使用Vue Router
自带的导航守卫创建中间件:const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
上面示例中的
beforeEach
导航守卫是一个全局导航守卫,它在导航到任何路由时被调用。除了全局守卫之外,还有特定路由的导航守卫,这正是我们将在下一节中更详细探讨的内容。所以,让我们开始吧!
如果你想了解更多关于 Vue Router
的信息,请访问 https://router.vuejs.org/zh/。
使用导航守卫
导航守卫用于保护你的应用中的导航。这些守卫允许我们在进入、更新和离开路由之前调用函数。当某些条件不满足时,它们可以重定向或取消该路由。有几种方式可以介入路由导航过程:全局的、单个路由的或组件内的。让我们在下一节中探讨全局守卫。
请注意,你可以在我们的 GitHub
仓库的 /chapter-11/vue/non-sfc/
中找到以下所有示例。
创建全局守卫
Vue Router
提供了两种全局守卫——全局前置守卫和全局后置守卫。让我们在将它们应用到我们的应用之前,先学习如何使用它们:
全局前置守卫 (Global before guards): 全局前置守卫在每次路由进入之前被调用。它们按照特定的顺序被调用,并且可以是异步的。导航会一直等待,直到所有的守卫都被解析完成。我们可以使用 Vue Router
的 beforeEach
方法注册这些守卫,如下所示:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => { ... })
全局后置守卫 (Global after guards): 全局后置守卫在每次路由进入之后被调用。与全局前置守卫不同,全局后置守卫没有 next
函数,因此它们不会影响导航。我们可以使用 Vue Router
的 afterEach
方法注册这些守卫,如下所示:
const router = new VueRouter({ ... })
router.afterEach((to, from) => { ... })
让我们创建一个包含一个简单 HTML
页面的 Vue
应用,并在以下步骤中使用这些守卫:
-
使用
<router-link>
元素创建两个路由,如下所示:<div id="app"> <p> <router-link to="/page1">Page 1</router-link> <router-link to="/page2">Page 2</router-link> </p> <router-view></router-view> </div>
-
定义路由的组件(
Page1
和Page2
),并在<script>
块中将它们传递给router
实例:const Page1 = { template: '<div>Page 1</div>' } const Page2 = { template: '<div>Page 2</div>' } const routes = [ { path: '/page1', component: Page1 }, { path: '/page2', component: Page2 } ] const router = new VueRouter({ routes })
-
在路由实例之后声明全局前置守卫和全局后置守卫,如下所示:
router.beforeEach((to, from, next) => { console.log('global before hook') next() }) router.afterEach((to, from,) => { console.log('global after hook') })
-
在守卫之后挂载根实例并运行我们的应用:
const app = new Vue({ router }).$mount('#app')
-
在浏览器中运行该应用,当你在路由之间切换时,你应该在浏览器控制台中看到以下日志:
global before hook global after hook
当你想要对所有路由应用一些通用的操作时,全局守卫非常有用。然而,有时我们只需要对特定的路由进行一些特定的操作。为此,你应该使用 per-route guards(单个路由守卫)。让我们在下一节学习如何部署它们。
创建路由前置的守卫
我们可以通过直接在路由配置对象的 beforeEnter
属性或方法中创建单个路由守卫。例如,请看以下代码:
beforeEnter: (to, from, next) => { ... }
// 或:
beforeEnter (to, from, next) { ... }
让我们复制之前的 Vue
应用,并更改路由的配置以使用这些单个路由守卫,如下所示:
const routes = [
{
path: '/page1',
component: Page1,
beforeEnter: (to, from, next) => {
console.log('before entering page 1')
next()
}
},
{
path: '/page2',
component: Page2,
beforeEnter (to, from, next) {
console.log('before entering page 2')
next()
}
}
]
当你导航到 /page1
时,你应该在浏览器控制台中看到 before entering page 1 的日志,而在 /page2
上时,你应该看到 before entering page 2 的日志。既然我们可以将守卫应用于页面的路由,那么将守卫应用于路由组件本身呢?答案是肯定的,我们可以。让我们继续下一节,学习如何使用组件内的守卫来保护特定的组件。
创建组件内的守卫
我们可以在路由组件内部单独或一起使用以下方法,为特定组件创建导航守卫。
beforeRouteEnter
守卫:
与全局前置守卫和单个路由的 beforeEnter
守卫类似,beforeRouteEnter
守卫在路由渲染组件之前被调用,但它应用于组件本身。我们可以使用 beforeRouteEnter
方法注册这种类型的守卫,如下所示:
beforeRouteEnter (to, from, next) { ... }
因为它在组件实例创建之前被调用,所以它无法通过 this
关键字访问 Vue
组件。但这可以通过将 Vue
组件的回调传递给 next
参数来解决:
beforeRouteEnter (to, from, next) {
next(vueComponent => { ... })
}
beforeRouteLeave
守卫:
相比之下,当路由渲染的组件即将导航离开时,会调用 beforeRouteLeave
守卫。由于它在 Vue
组件渲染之后被调用,因此可以通过 this
关键字访问 Vue
组件。我们可以使用 beforeRouteLeave
方法注册这种类型的守卫,如下所示:
beforeRouteLeave (to, from, next) { ... }
通常,这种类型的守卫最适合用于防止用户意外离开路由。因此,可以通过调用 next(false)
来取消导航:
beforeRouteLeave (to, from, next) {
const confirmed = window.confirm('你确定要离开吗?')
if (confirmed) {
next()
} else {
next(false)
}
}
beforeRouteUpdate
守卫:
当路由渲染的组件已更改,但该组件在新路由中被复用时,会调用 beforeRouteUpdate
守卫;例如,如果你有使用相同路由组件的子路由组件:/page1/foo
和 /page1/bar
。因此,从 /page1/foo
导航到 /page1/bar
将触发此方法。并且由于它在组件渲染之后被调用,因此可以通过 this
关键字访问 Vue
组件。我们可以使用 beforeRouteUpdate
方法注册这种类型的守卫:
beforeRouteUpdate (to, from, next) { ... }
请注意,beforeRouteEnter
方法是唯一在 next
方法中支持回调的守卫。在调用 beforeRouteUpdate
和 beforeRouteLeave
方法之前,Vue
组件已经可用。因此,在这两个方法中使用 next
方法中的回调是不支持的,因为它是没有必要的。因此,如果你想访问 Vue
组件,只需使用 this
关键字:
beforeRouteUpdate (to, from, next) {
this.name = to.params.name
next()
}
现在,让我们创建一个包含一个简单 HTML
页面的 Vue
应用,使用以下守卫:
-
创建一个页面组件,其中包含
beforeRouteEnter
、beforeRouteUpdate
和beforeRouteLeave
方法,如下所示:const Page1 = { template: '<div>Page 1 {{ $route.params.slug }}</div>', beforeRouteEnter (to, from, next) { console.log('before entering page 1') next(vueComponent => { console.log('before entering page 1: ', vueComponent.$route.path) }) }, beforeRouteUpdate (to, from, next) { console.log('before updating page 1: ', this.$route.path) next() }, beforeRouteLeave (to, from, next) { console.log('before leaving page 1: ', this.$route.path) next() } }
-
创建另一个仅包含
beforeRouteEnter
和beforeRouteLeave
方法的页面组件,如下所示:const Page2 = { template: '<div>Page 2</div>', beforeRouteEnter (to, from, next) { console.log('before entering page 2') next(vueComponent => { console.log('before entering page 2: ', vueComponent.$route.path) }) }, beforeRouteLeave (to, from, next) { console.log('before leaving page 2: ', this.$route.path) next() } }
-
在初始化
router
实例之前定义主路由和子路由,如下所示:
const routes = [
{
path: '/page1',
component: Page1,
children: [
{
path: ':slug'
}
]
},
{
path: '/page2',
component: Page2
}
]
理解导航守卫的参数:to, from, 和 next
你在前面章节中使用的导航守卫中已经见过这些参数,但我们尚未详细介绍它们。除了 afterEach
全局守卫之外,所有守卫都使用这三个参数:to
、from
和 next
。
to 参数:
此参数是你导航到的路由对象(因此,它被称为 to
参数)。此对象包含 URL
和路由的解析信息:
name |
meta |
path |
hash |
query |
params |
fullPath |
matched |
如果你想了解更多关于这些对象属性的信息,请访问 https://router.vuejs.org/api/the-route-object 。
next 参数:
此参数是一个你必须调用的函数,用于移动到队列中的下一个守卫(中间件)。如果你想中止当前的导航,你可以将一个布尔值 false
传递给这个函数: next(false)
如果你想重定向到不同的位置,你可以使用以下代码行:
next('/')
// 或
next({ path: '/' })
如果你想使用 Error
的实例中止导航,你可以使用以下代码行:
const error = new Error('发生了一个错误!')
next(error)
然后,你可以在根组件中捕获该错误:
router.onError(err => { ... })
现在,让我们创建一个包含一个简单 HTML
页面的 Vue
应用,并在以下步骤中实验 next
函数:
-
使用
beforeRouteEnter
方法创建以下页面组件,如下所示:const Page1 = { template: '<div>Page 1</div>', beforeRouteEnter (to, from, next) { const error = new Error('发生了一个错误!') error.statusCode = 500 console.log('before entering page 1') next(error) } } const Page2 = { template: '<div>Page 2</div>', beforeRouteEnter (to, from, next) { console.log('before entering page 2') next({ path: '/' }) } }
在上面的代码中,我们为
Page1
将Error
实例传递给next
函数,同时为Page2
将路由重定向到主页。 -
在初始化
router
实例之前定义路由,如下所示:const routes = [ { path: '/page1', component: Page1 }, { path: '/page2', component: Page2 } ]
-
创建
router
的实例并使用onError
方法监听错误:const router = new VueRouter({ routes }) router.onError(err => { console.error('正在处理此错误:', err.message) console.log(err.statusCode) })
-
使用
<router-link>
Vue 组件创建以下导航链接:<div id="app"> <ul> <li><router-link to="/">Home</router-link></li> <li><router-link to="/page1">Page 1</router-link></li> <li><router-link to="/page2">Page 2</router-link></li> </ul> <router-view></router-view> </div>
-
在浏览器中运行该应用,当你在路由之间切换时,你应该在浏览器控制台中看到以下日志:
-
当从
/
导航到/page1
时,你应该看到以下内容:before entering page 1 Handling this error: An error occurred! 500
-
当从
/page1
导航到/page2
时,你应该看到以下内容:before entering page 2
-
你还会注意到,由于这行代码:next({ path: '/' })
,当你从 /page1
导航到 /page2
时,你会被定向到 /
。
到目前为止,我们是在一个简单的 HTML
页面中创建中间件。然而,在实际项目中,我们应该尝试使用你在之前的章节中学习过的 Vue
单文件组件 (SFC
) 来创建它们。因此,在下一节中,你将学习使用 Vue CLI
在 Vue SFC
中创建中间件,而不是你到目前为止学习过的自定义 webpack
构建过程。所以,让我们开始吧。