Vue系列之—Vue-router详解
一、简介
Vue-router 是 Vue.js
官方的路由管理器。它和 Vue.js
的核心深度集成,让构建单页面应用变得易如反掌
先来了解两点
-
单页面应用(SPA)
-
1.1 单页面应用
单页面应用程序将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript 和 CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用 JavaScript 动态的变换HTML的内容,从而实现UI与用户的交互。
1.2 路由管理器
这里的路由管理器并不是我们平时生活中的硬件路由器,这里的路由就是单页应用(SPA)的路径管理器,就是为了解决Vue.js开发单页面应用不能进行链接跳转,我们通过路径的的方式来管理不同的页面
了解Vue-router所解决的问题之后, 我们开始学习Vue-router在项目中常用的一些功能:
-
嵌套的路由/视图表
-
模块化的、基于组件的路由配置
-
路由参数、查询、通配符
-
细粒度的导航控制
二、起步
在开始我们先体会下Vue-router的一些功能:
-
动态路由匹配
-
嵌套路由
-
声明式/编程式导航
-
命名路由/命名视图
-
重定向和别名
-
路由组件传参
2.1 动态路由匹配
router.js
import Vue from 'vue' // 引入Vue import Router from 'vue-router' // 引入vue-router import Home from '@/pages/Home' //引入根目录下的Hello.vue组件 // Vue全局使用Router Vue.use(Router) /*使用 Vue.js + Vue-router构建单页面应用, 只要通过组合组件来组成我们的应用程序, 我们引入Vue-router,只要将组件映射到路由,告诉Vue-router在那里渲染它们 */ let routes = [ // 配置路由,这里是个数组 { // 每一个链接都是一个对象 path: '/', // 链接路径 name: 'Home', // 路由名称, component: Home // 对应的组件模板 }, // 动态路径参数 以冒号开头 { path: '/user/:username', // 动态路由 component: () => import('../pages/User1'), // 按需加载路由对应的组件, 需要下载polyfill兼容ES6语法 }, { // 多段路径参数 path: '/user/:id/post/:post_id', // 动态路由 component: () => import('../pages/User2'), // 按需加载路由对应的组件, 需要下载polyfill兼容ES6语法 }, ] export default new Router({ routes })
User1 用户访问 /#/user/xxx的时候展示该组件 <template> <div class="User1"> User1 - 单个路径参数 </div> </template> User2 用户访问 /#/user/xxx/post/xxx的时候展示该组件 <template> <div class="User2"> User2 - 多段路径参数路由 </div> </template>
那么问题来了,我们怎么知道用户访问的是那个动态参数路由呢?这个时候就要用到响应路由参数的变化
两种方式:watch (监测变化) $route
对象, beforeRouteUpdate
导航守卫
向user1.vue
增加下面代码
<template> <div class="User1"> <!-- 通过router对象可以获取到路由属性, 这种方式耦合了,后面会讲路由组件传参的方式 --> User1 -{{$route.params.username}} 单个路径参数 </div> </template> <script> export default { name: 'User1', // 侦听route对象方式 watch: { $route (to, from) { this.$message.success(`watch -> ${to.path}, ${from.path}`) }, }, // vue2.2引入的导航守卫,当路由参数发生变化调用 beforeRouteUpdate (to, from, next) { this.$message.info(`导航守卫 -> ${to.path}, ${from.path}`) // 一定要调用next让其继续解析下一个管道中的路由组件 next() } } </script>
2.2 路由组件传参
上面在<tempate>
模板中通过$router.prarams.username
方式获取路由传递的参数已经于其对应路由形成高度耦合,限制了其灵活性, 我们可以通过props
将组件和路由进行解耦
props传递路由组件参数有三种方式:
-
布尔模式
-
对象模式
-
函数模式
代码
router.js
import Vue from 'vue' import Router from 'vue-router' import home from '@/pages/Home' Vue.use(Router) let routes = [ { path: '/', name: 'Home', component: home }, { // 动态路径参数 以冒号开头 path: '/user1/:username', // 动态路由 component: () => import('../pages/User1'), props: true // 布尔模式: 如果 props 被设置为 true,route.params 将会被设置为组件属性。 }, { path: '/user2', component: () => import('../pages/User2'), props: {username: 'ck'} // 对象模式: 只有静态路由才能有效, 并且参数是写死的 }, { path: '/user3/:username', component: () => import('../pages/User3'), // 返回了用户url中的参数 比如 /user3?username='ck' => {username: 'ck} 最终还是以对象模式的方式返回参数 props: (route) => ({username: route.query.username}) // 函数模式 } ] export default new Router({ routes })
User1 布尔模式 <template> <div class="User1"> User1 -{{username}} </div> </template> <script> export default { name: 'User1', props: ['username'] // 通过props获取路由传递给对应组件的参数 } </script>
User2 对象模式 <template> <div class="User2"> User2 - {{username}} </div> </template> <script> export default { name: 'User2', props: ['username'] // 通过props获取路由传递给对应组件的参数 } </script>
User3 函数模式 <template> <div class="User3"> User3 - {{username}} </div> </template> <script> export default { name: 'User3', props: ['username'] // 通过props获取路由传递给对应组件的参数 } </script>
演示
从上面我们可以看出因为user2是静态路由所以不支持动态参数而且其对应的路由组件传递过来的参数也是写死的
2.3 嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:
router.js
import Vue from 'vue' import Router from 'vue-router' import home from '@/pages/Home' Vue.use(Router) let routes = [ { path: '/', name: 'Home', component: home, }, { path: '/user/:username', // 动态路由 name: 'User', component: () => import('../components/User'), children: [ { // 当 '/user/:username/profile' 匹配成功, UserProfile 会被渲染在 User 的 <router-view> 中 path: 'profile', // 可以匹配 /user/ks/profile name: 'Profile', component: () => import('../components/Profile') }, { path: '/user/:usrname/posts', // 这样也可以匹配 /user/ks/posts, 但其实是将其匹配为了根组件的/user:username动态组件下的 posts name: 'Posts', component: () => import('../components/Posts') }, { path: '', name: 'UserHome', // 当 /user/:username 匹配成功,比如 /user/ks || /user/ck // UserHome 会被渲染在 User 的 <router-view> 中 component: () => import('../components/UserHome') }, ] }, { path: '/footer', name: 'Foo', component: () => import('../components/Footer') } ] export default new Router({ routes })
演示
声明式/编程式导航
声明式 | 编程式 |
---|---|
<router-link :to="..." replace> |
router.replace(...) |
<template> <div class="home"> <!-- 声明式 --> <router-link to="footer" tag="button" > to footer </router-link> <!-- 编程式 --> <button @click="$router.push('footer')">字符串-写路由路径的方式</button> <button @click="$router.push({path: '/footer'})">对象-写路径的方式</button> <button @click="$router.push({name: 'Foo', params: {'userId': '123'}})">name和params - 写路由名称携带参数的方式</button> <button @click="$router.push({path: '/footer', query: {'userId': '456'}})">queyr和path - 写路由路径携带参数的方式</button> <router-view></router-view> </div> </template> <script> export default { name: 'home', data () { return { } }, methods: { } } </script> <style> button { display: block; } </style>
-
router.push(location, onComplete?, onAbort?)
-
router.replace(location, onComplete?, onAbort?)
这两种的方式一样, 唯一区别在于 push
会产生路由的历史记录, 而repalce
不会产生, 这对于window中的history
是一致的
<!-- router.go方法 --> <template> <button @click="goForward">go(1)-前进一步</button> <button @click="goBack">go(-1)-后退一步</button> <button @click="gogogo">go(100)-前进一白步</button> </template> <script> export default { name: 'home' methods: { goForward () { // 从历史路由中前进一步相当于 window.history.forward this.$router.go(1); }, goBack () { // 从历史路由中后退一步相当于 window.history.back this.$router.go(-1); }, gogogo () { // 历史路由中没有100步, 就啥也不干 this.$router.go(100); } } } </script>
演示
命名路由/命名视图/重定向和别名
router.js
import Vue from 'vue' import Router from 'vue-router' import UserSettings from '@/pages/UserSettings' Vue.use(Router) let routes = [ { path: '/', redirect: '/settings' // 重定向 }, { path: '/settings', name: 'Settings', // 命名路由 alias: '/a', // 取别名,当url中访问 /a -> 也是访问的 settings组件但是路由匹配的是/a, 就相当于用户访问 /a一样 // 你也可以在顶级路由就配置命名视图 component: UserSettings, children: [ { path: 'emails', component: () => import('../pages/UserEmails') }, { path: 'profile', components: { default: () => import('../pages/UserProfile'), helper: () => import('../pages/UserProfilePreview') } } ] } ] export default new Router({ routes })
UserSetttings
<template> <div class="UserSettings"> <h1>User Settings</h1> <NavBar/> <router-view/> <!-- 命名视图 --> <router-view name="helper"/> </div> </template> <script> import NavBar from '../components/NavBar' export default { name: 'UserSettings', components: { NavBar } } </script>
通过上面的学习相信大家已经撑握了Vue-router在项目中所常用的功能,下面我们开始学习Vue-router的导航守卫
三、进阶
导航守卫
“导航”表示路由正在发生改变。记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察
$route
对象响应路由参数的变化来应对这些变化,或使用beforeRouteUpdate
的组件内守卫。
全局的守卫
-
全局前置守卫 (router.beforeEach)
-
全局解析守卫 (router.breforeResolve)
-
全局后置钩子 (router.afterEach) 注:这个钩子中不存在next
路由独享的守卫
你可以在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // to -> 要跳转过去的路由信息 // from -> 当前的路由信息 // next() => 一个函数,表示解析下一个管道中的路由记录 } } ] })
组件内的守卫
最后,你可以在路由组件内直接定义以下路由导航守卫:
-
beforeRouteEnter
-
beforeRouteUpdate
(2.2 新增) -
beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } }
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。不过,你可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) { next(vm => { // 在实例创建好之后会调用next传递过去的回调并且将实例当做参数传递进来,所以通过 `vm` 可以访问组件实例 }) }
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。】
beforeRouteLeave (to, from, next) { const answer = window.confirm('Do you really want to leave? you have unsaved changes!') if (answer) { next() } else { next(false) } }