一文掌握Vue-Router!!!!

63 阅读6分钟

Vue-Router

Vue-router做路径匹配时支持动态片段、全匹配片段以及查询参数(片段指的是URL中的一部分)

对于解析过的路由,这些信息都可以通过路由上下文对象(从现在起,我们会称其为路由对象)访问。

在使用了vue-router的应用中,路由对象会被注入每个组件中,赋值为this.$route,并且当路由切换时,路由对象会被更新


脚手架中添加vue-router

  • 在路由定义文件中,通过Vue.use添加组件选项,并且该方法必须在new Vue() 启动应用之前完成

Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router);
export default new Router({
  mode: 'history', // 去掉路由中的#号
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
  ]
})
  • Vue.new初始化时,将可导出router对象注入到Vue根实例中
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router, // 注入router
  components: {App},
  template: '<App/>'
})

这样就可以了

路由模式

一般单页面应用是(SPA)不会请求页面而是只更新视图

vue-router提供了两种方式来实现前端路由:Hash模式和History模式,可以用mode参数来决定使用哪一种方式

export default new Router({
  mode: 'history',
  ...
})

hash

vue-router默认使用Hash模式,使用urlhash来模拟一个完整的url

此时url变化时,浏览器是不会重新加载的

Hash(即#)url的锚点,代表的是网页中的一个位置,仅仅改变#后面部分,浏览器只会滚动对应的位置,而不会重新加载页面

#仅仅只是对浏览器进行指导,而对服务端是完全没有作用的

它不会被包括在http请求中,故也不会重新加载页面。同时hash发生变化时,url都会被浏览器记录下来,这样你就可以使用浏览器的后退了

History

如果你不喜欢hash这种#样式,可以使用history模式

这种模式利用了HTML5 History新增的pushState()replaceState()方法

除了之前的backforwardgo方法;这两个新方法可以应用在浏览器历史记录的增加替换功能上

使用History模式,通过历史记录修改url。但它不会立即向后端发送请求


注意:虽然History模式可以丢掉不美观的#,也可以正常的前进后退,但是刷新f5后,此时浏览器就会访问服务器,在没有后台支持的情况下,此时就会得到一个404

官方文档给出的描述是: 这种模式要玩好,还需要后台配置支持


因为我们的应用是单个客户端应用,如果后台没有正确的配置,当用户直接访问时,就会返回404

所以,要在服务端增加一个覆盖所有情况的的候选资源。如果url匹配不到任何静态资源;则应该返回同一个index.html页面

路由属性

路由对象this.$route支持属性如下

  • $route.path

    字符串,等于当前路由对象的路径,会被解析为绝对路径

/aaa/bbb
  • $route.params

    包含路由中的动态片段和全匹配片段的键值对,配合动态路由使用

  • $route.query

    获取连接中查询参数的键值对

/foo?user=1
// 可以获取到
$route.query.user == 1
  • $route.router

    路由规则所属的路由器以及其所属的组件

  • $route.matched

    数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象

  • $route.name

    当前路径的名字

自定义属性

路由中设置自定义参数

router.map({
  '/a': {
    component: { ... },
    auth: true // 这里 auth 是一个自定义字段
  }
})

/a 被匹配时,$route.auth 的值将会是 true

我们可以利用这个特性在全局的钩子函数中进行身份验证

router.beforeEach(function (transition) {
  if (transition.to.auth) {
    // 对用户身份进行验证...
  }
})

动态路由

:号通配符

路由中的动态片段使用以冒号开头的路径片段定义,类比django中的路由传参

path('<int:name>/',views.xx),
path: '/user/:username',
component: {
    template: '<p>用户名是{{$route.params.username}}</p>'
}

一条路径中可以包含多个动态片段,每个片段都会被解析成 $route.params 的一个键值对

解析模式

模式匹配的路径$route.params
/user/:username/user/evan{ username: 'evan' }
/user/:username/post/:post_id/user/evan/post/123{ username: 'evan', post_id: 123 }

全匹配路由

*号通配符

动态片段只能匹配路径中的一个部分,而全匹配片段则基本类似于它的贪心版

例如 /foo/*bar 会匹配任何以 /foo/ 开头的路径

当使用一个通配符时,$route.params内会自动添加一个名为pathMatch的参数,其中包含了在连接中通过通配符所匹配到的部分

{
    path: '/user/*/cc',
    name: 'User',
    component: User,
},

访问的URL如下

http://127.0.0.1:8080/user/1231/aaa/cc

那么此时*通配符拿到的部分为

{ "pathMatch": "1231/aaa" }

命名路由

类似django的路由命名,vue路由映射中也可以为某一个路由通过name属性设置命名

{
    path: '/user/:userid',
    name: 'User',
    component: User,
},

在使用v-link标签进行路由跳转时,就可以更加方便啦

<router-link :to="{name: 'User', params: {userid: 'zhangsan' }}">user</router-link>

也可以在js代码中使用router.go切换到该路径下

router.go({ name: 'user', params: { userId: 123 }})

路由跳转

v-link 是一个用来让用户在vue-router应用的不同路径间跳转的指令

该指令接受一个JavaScript表达式,并会在用户点击元素时用该表达式的值去调用 router.go

<!-- 字面量路径 -->
<a v-link="'home'">Home</a>

<router-link :to="{name: 'home'}">Home</router-link>

<!-- 效果同上 -->
<a v-link="{ path: 'home' }">Home</a>

<!-- 具名路径 -->
<a v-link="{ name: 'user', params: { userId: 123 }}">User</a>

一般来说,都应该使用 v-link 而不是 href 来处理浏览时的跳转,这是因为

  • 它在HTML5 history模式和hash模式下的工作方式相同,所以如果你决定改变模式,或者IE9浏览器退化为hash模式时,都不需要做任何改变
  • HTML5 history模式下,v-link 会监听点击事件,防止浏览器尝试重新加载页面
  • HTML5 history模式下使用 root 选项时,不需要在 v-linkURL中包含root路径

其他选项

  • replace

    一个带有 replace: true 的链接被点击时将会触发 router.replace() 而不是 router.go()。由此产生的跳转不会留下历史记录

<router-link :to="{name: 'User', params: {userid: 'zhangsan' }, replace: true }">replace</router-link>
  • append

    带有 append: true 选项的相对路径链接会确保该相对路径始终添加到当前路径之后

    举例来说,从 /a 跳转到相对路径 b 时,如果没有 append: true 我们会跳转到 /b,但有 append: true 则会跳转到 /a/b

<router-link :to="{name: 'User', append: true }">append</router-link>

路由钩子

参考: router.vuejs.org/zh/

  1. 导航被触发
  2. 在失活的组件里调用离开守卫
  3. 调用全局的 beforeEach 守卫
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)
  9. 导航被确认
  10. 调用全局的 afterEach 钩子
  11. 触发 DOM 更新
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数

全局前置钩子

beforeEach(to, from, next)

添加一个全局的前置钩子函数,可以在Router对象初始化时,这样钩子方法会在路由切换开始时调用

调用发生在整个切换流水线之前。如果此钩子函数拒绝了切换,整个切换流水线根本就不会启动,想当于页面不会进行跳转

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

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

export default router

to: 即将要进入的目标 路由对象 from: 当前导航正要离开的路由 next: 实际路由前进的控制方法,调用该方法将执行此次路由跳转,可以传入类似v-link类似的跳转选项参数


如果需要对某些需要用户验证的路由进行身份识别,那么可以像下面这样

router.beforeEach((to, from, next) => {
  if(to.meta.requiredAuth == true){ // 如果要进入的路由需要用户登陆
    if(window.localStorage.getItem('token')){ // 获取存储的 token
      next()
    } else{ // 没有获取到,则跳转到登陆页面
      next({
        path: '/login',
        query: toQuery,
      })
    }
  }else{
    next()
  }
})

全局后置钩子

在完成路由跳转后调用,无next参数改变接下来的前进路由

router.afterEach((to, from) => {
  // ...
})

局部路由钩子

定义局部路由钩子,只需要在路由组件映射的位置编写即可,对某个路由映射生效

const router = new VueRouter({
  routes: [
    {
      path: '/',
      component: Index,
      beforeEnter: (to, from, next) => {
        // 只对当前路由生效
      }
    }
  ]
})

路由元信息

在定义路由时,可以在路由映射中使用meta进行元属性的定义

比如每个页面组件都可以具有不同的title,那么就可以定义在路由中

const router = new VueRouter({
  routes: [
    {
      path: '/index',
      component: Index,
      meta: {title: index} // 元属性
    }
  ]
})

组件中获取元属性

$route.meta.title

路由钩子中获取元属性

router.beforeEach((to, from, next) => {
  const title = to.meta.title;
  document.title = title;
  next();
})

路由嵌套

当我们发现,编写的组件为一些全局的公共部分,比如后台的左侧导航,除了使用组件封装复用的思路,还可以通过路由嵌套实现代码复用

比如编写一个组件,它具有一个父路由,通过子路由嵌套的方式,可以让所有符合匹配规则的子路由对应的组件渲染在当前组件中


一个导航组件Layout,是这样的

<template>
	<section class="app-main">
        <div>
            这就是导航  
        </div>
        <router-view :key="key" /> 
    </section>
</template>

可以看到,我们使用了router-view标签,这将渲染所有当前组件嵌套路由下的内容

比如当前的导航组件是这样的路由

routes: [
    {
        path: '/',
        component: Layout,
		...
    }
]

那么嵌套路由可以通过children属性进行定义,未来我们其他需要Layout的组件都可以成为这个路由的子路由

参考官方文档的定义方式,如下所示

routes: [
    {
        path: '/',
        component: Layout,
        children: [
        {
          // 当 /profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
]

children 配置就是像 routes 配置一样的路由配置数组,所以呢,你可以嵌套多层路由