本文基于
[1] vue-Router 3.x官方文档
[2] 掘金ID:shotCat 可能比文档还详细--VueRouter完全指北
链接:https://juejin.cn/post/6844903665388486664
整理而成

概述

  • vue-router和vue.js是深度集成的,适合用于单页面应用(SPA).
  • 传统的路由是用一些超链接来实现页面切换和跳转.而vue-router在单页面应用中,则是组件之间的切换.
  • 其本质就是:建立并管理url和对应组件之间的映射关系.

基础概念

html

  1. router-link组件来导航,用户点击后切换到相关视图.
  2. router-view组件来设置切换的视图在哪里渲染.(一个页面也可以有多个router-view分别展示特定的视图,并且支持嵌套)

JavaScript

通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route访问当前路由对象

$router和$router,routes的区别

  • $router是指整个路由实例,你可以操控整个路由,通过'$router.push'往其中添加任意的路由对象.
  • $route:是指当前路由实例('$router')跳转到的路由对象;
  • routes:指router路由实例的routes API.用来配置多个route路由对象.
  • 路由实例可以包含多个路由对象.它们是父子包含关系.

vue-router的两种模式

一般单页面应用是(SPA)不会请求页面而是只更新视图. vue-router提供了两种方式来实现前端路由:Hash模式和History模式,可以用mode参数来决定使用哪一种方式

hash模式

  • vue-router默认使用Hash模式.
  • 使用url的hash来模拟一个完整的url.此时url变化时,浏览器是不会重新加载的.Hash(即#)是url的锚点,代表的是网页中的一个位置,仅仅改变#后面部分,浏览器只会滚动对应的位置,而不会重新加载页面.#仅仅只是对浏览器进行指导,而对服务端是完全没有作用的!它不会被包括在http请求中,故也不会重新加载页面.同时hash发生变化时,url都会被浏览器记录下来,这样你就可以使用浏览器的后退了.
  • 总而言之:Hash模式就是通过改变#后面的值,实现浏览器渲染指定的组件.

History模式

  • 如果你不喜欢hash这种#样式.可以使用history模式.这种模式利用了HTML5 History新增的pushState()和replaceState()方法.
  • 除了之前的back,forward,go方法,这两个新方法可以应用在浏览器历史记录的增加替换功能上.使用History模式,通过历史记录修改url,但它不会立即向后端发送请求.
  • 注意点:虽然History模式可以丢掉不美观的#,也可以正常的前进后退,但是刷新f5后,此时浏览器就会访问服务器,在没有后台支持的情况下,此时就会得到一个404!官方文档给出的描述是:"不过这种模式要玩好,还需要后台配置支持.因为我们的应用是单个客户端应用,如果后台没有正确的配置,当用户直接访问时,就会返回404.所以呢,你要在服务端增加一个覆盖所有情况的的候选资源;如果url匹配不到任何静态资源,则应该返回同一个index.html页面."
  • 总而言之:History模式就是通过pushState()方法来对浏览器的浏览记录进行修改,来达到不用请求后端来渲染的效果.不过建议,实际项目还是使用history模式.
const router = new VueRouter({
  mode: 'history', //如果这里不写,路由默认为hash模式
  routes: [...]
})

动态路由匹配

当我们经常需要把某种模式匹配到的所有的路由,全部都映射到同个组件.例如:我们有一个User组件,对于所有ID各不相同的用户,都要使用这个组件来渲染.这时我们就可以配置动态路由来实现.
动态路由匹配本质上就是通过url进行传参

路由对象属性介绍

为了下面理解的方便这里简单介绍下常用的路由对象属性,在组件内可以通过this.$route(不是$router!)进行访问.

  • $route.path 类型: string 字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。
  • $route.params 类型: Object 一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
  • $route.query 类型: Object 一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
  • $route. name 当前路由的名称,如果有的话。这里建议最好给每个路由对象命名,方便以后编程式导航.不过记住name必须唯一!
  • $route.hash 类型: string 当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
  • $route.fullPath 类型: string 完成解析后的 URL,包含查询参数和 hash 的完整路径。
  • $route.matched
    类型:Array<RouteRecord>

一个数组,包含当前路由的所有嵌套路径片段的路由记录。路由记录就是routes配置数组中的对象副本 (还有在children数组)。

  • $route.redirectedFrom
    如果存在重定向,即为重定向来源的路由的名字。

使用params进行配置

举个例子:

routes:[{
    //动态路径参数,以冒号开头
    path:'/user/:id',
    component:User
}]

这样,就是使用params进行配置.像/user/foo和/user/bar都将映射到相同的路由.

  • 一个路径参数使用':'冒号进行标记.
  • 当匹配到一个路由时,参数就会被设置到this.$route.params,可以在每个组件内使用.你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 this.$route.params 中。例如/user/foo在this.$route.params.id的值就为foo

这里以官方的表格示例进行展示

模式匹配路径$route.params
/user/:username/user/evan{username:'evan'}
/user/:username/post/:post_id/user/evan/post/123{username:'evan',post_id:123}
  • 有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序.谁先定义的,谁的优先级就最高.
  • 由于路由参数对组件实例是复用的.例如:/user/foo 和 /user/bar在使用路由参数时,复用的都是User组件.此时组件的生命周期钩子不会再被调用。如果你想路径切换时,进行一些初始化操作时,可以用以下两种解决办法:

    1. 在组件内 watch $route 对象:
const User = {
 template: '...',
 watch: {
   '$route' (to, from) {
     // 对路由变化作出响应...
   }
 }
}
  1. 使用2.2版本中的 beforeRouteUpdate 路由守卫
const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

通过query进行配置传参

在项目里我们可以通过上面提到的params进行传参.同时也可以用query进行传参.
举个例子:

<router-link to="/user?id=foo">foo</router-link>

vue-route会自动将?后的id=foo封装进this.$route.query里. 此时,在组件里this.$route.query.id值为'foo'.
==除了通过router-link的to属性. query也可以通过后面讲到的编程式导航进行传参==

编程式导航

编程式导航就是在vue组件内部通过this.$router访问路由实例,并通过this.$router.push导航到不同的url,进行路由映射,所以 它的作用是和<router-link :to>一样的 当然,前提是你已经在routes里配置了对应的路由对象.

什么时候用到编程式导航?

如果,你想在路由跳转前做点其他事情,例如权限验证等.但是用<router-link>的话,就直接跳转了.此时就可以使用编程式导航.

写法

编程式导航一般都是用到router.push方法.该方法的参数可以是一个字符串路径,或者一个描述地址的对象.例如:

//字符串
this.$router.push('home')

//对象
this.$ruter.push({path:'home'})

//命名路由
this.$router.push({name:'user',params:{userId:2333}})

//带查询参数,变成/register?plan=private
this.$router.push({path:'register',query:{plan:'private'}})

注意:path和params是不能同时生效的.否则params会被忽略掉.所以使用对象写法进行params传参时,要么就是path加冒号:,要么就是像上例中的'命名路由'.通过name和params进行传参.然而query却并不受影响,有没有path都可以进行传参.

router.replace方法

router.replace和router.push很像,写法一样.但实际效果不一样.push是向history里添加新记录.而replace是直接将当前浏览器history记录替换掉!
那最直接的后果是什么呢? 举个例子:

  • 用push方法,页面1跳转到页面2,你使用浏览器的后退可以回到页面1
  • 用replace方法,页面1被替换成页面2,你使用浏览器的后退,此时你回不到页面1,只能回到页面1的前一页,页面0.

那什么时候会用到replace呢?

当你不想让用户回退到之前的页面时,常见于权限验证,验证后就不让用户回退到登录页重复验证.

router.go(n)方法

这个方法的参数就是一个整数,意思是在history记录中前进或后退多少步.类似window.history.go(n).这样就能控制页面前进或者后退多少步.

实际上不通过routes配置,也可以用下面这种方法直接在router-link上通过to进行传参

routes:[
    {name:'shotCat',path:'/shotCat', component:shotCat}
]


<router-link :to="{ name:'shotCat',params:{paramId:'hello'},query:{queryId:'world'}}">helloWorld</router-link> <!--此时通过name匹配到路由对象shotCat.-->     
<router-link :to="{ path:'/shotCat',params:{paramId:'hello'},query:{queryId:'world'}}">helloWorld</router-link>  <!--此时通过path匹配到路由对象shotCat.但是!!!!!此时`paramId`并不能添加到`$route.params`里,只有`queryId`成功添加到`$route.query`-->

so:通过两个router-link.可以发现这种写法和编程式导航的规则一毛一样, path和params是不能同时生效的! 所以建议大家最好给每个路由对象进行命名

  • query是path和name都可以正常传参的.

小结

  • <router-link :to="{ }">等同于this.$router.push(). path和params是不能同时存在的!,想通过params,就得加上name属性.query不受影响.
  • params参数都不会显示在url地址栏中.除了在路由中通过routes进行配置的.所以用户刷新页面后,params参数就会丢失!
  • query参数可以正常显示在url地址栏中.刷新页面后也不会丢失

嵌套路由与单组件多视图

嵌套路由:就是父路由嵌套子路由.url上就是/user嵌套两个子路由后就是/user/foo和/uer/bar.用一张图表示就是:

单组件多视图:就是一个组件里有多个视图进行展示.即包含有多个<router-view/>

嵌套路由

一个<router-view/>对应展示的就是一个组件 因此实现嵌套路由有两个要点:

  1. 路由对象中定义子路由(嵌套子路由)
  2. 组件内<router-view/>的使用.

路由对象中定义子路由

const router = new VueRouter({
  routes: [
    { path: '/user', component: User,name:'user',
    //嵌套路由就写在children配置中,写法和rutes一样.
      children: [
        { path: '', component: UserDefault ,name:'default',
            //children:[{}]   也可以继续添加children嵌套
        },
        //如果/user下没有匹配到其他子路由时,User的<router-view>是什么都不会显示的,如果你想让它显示点什么.可以将path:''.设为空.此时UserDefault就是默认显示的组件.
        
        { path: 'foo', component: UserFoo,name:'foo'},
        //此时path等同于'/user/foo',子路由会继承父路由的路径.但是不能写成path:'/foo'.因为以 / 开头的嵌套路径会被当作根路径,也就是说此时foo成了根路径.而不是user.
        
        { path: 'bar', component: UserBar,name:'bar' }
      ]
    }
  ]
})
 

组件内<router-view/>的使用

html代码:

<div id="app">
 <p>
   <router-link to="/user">/user</router-link>
   <router-link to="/user/foo">/user/foo</router-link>
   <router-link to="/user/bar">/user/bar</router-link>
 </p>
 <router-view></router-view> <!--这里展示的是User组件;同样User的<router-view/>也被嵌套在里面-->
</div>
 

js代码:

const User = {
 template: `
   <div class="user">
     <h2>User</h2>
     <router-view></router-view> 
   </div>
 `
}
//User的<router-view>里展示的就是子路由foo,bar的组件还有default默认组件

const UserDefault = { template: '<div>default</div>' }
const UserFoo = { template: '<div>foo</div>' }
const UserBar = { template: '<div>bar</div>' }
 

嵌套路由搭配动态路由使用

html代码:

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <p>
    <router-link to="/user/foo">/user/foo</router-link>
    <router-link to="/user/foo/profile">/user/foo/profile</router-link>
    <router-link to="/user/foo/posts">/user/foo/posts</router-link>
  </p>
  <router-view></router-view>
</div>

js代码:

const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `
}

const UserHome = { template: '<div>Home</div>' }
const UserProfile = { template: '<div>Profile</div>' }
const UserPosts = { template: '<div>Posts</div>' }

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        // UserHome will be rendered inside User's <router-view>
        // when /user/:id is matched
        { path: '', component: UserHome },
                
        // UserProfile will be rendered inside User's <router-view>
        // when /user/:id/profile is matched
        { path: 'profile', component: UserProfile },

        // UserPosts will be rendered inside User's <router-view>
        // when /user/:id/posts is matched
        { path: 'posts', component: UserPosts }
      ]
    }
  ]
})

const app = new Vue({ router }).$mount('#app')

单组件多视图

如果一个组件有多个视图,来展示多个子组件.这个时候就需要用到命名视图
html部分:

 
<div id="app">
 <h1>Named Views</h1>
 <p>
   <router-link to="/avenger">复仇者联盟</router-link>
 </p>
 <router-view ></router-view>
 <router-view name="ironMan"></router-view>
 <router-view name="captainAmerica"></router-view>
</div>
<!--这里我们给其中两个视图命名为ironMan和captainAmerica;没有设置name的视图,会获得默认命名为default>
 

js部分:

 
const router = new VueRouter({
  routes: [
    { path: '/avenger', name:'avenger',components: {
        default: stanLee,
        ironMan: ironMan,
        captainAmerica: captainAmerica
      }
    //如果有多个视图需要展示时,以前的component换成components(加上s!!),写成对象形式.左边的ironMan指的就是<router-view>里设置的name="ironMan";右边的则指的是下面的组件ironMan.
    }
  ]
})

const stanLee = { template: '<div>斯坦李</div>' }
const ironMan = { template: '<div>钢铁侠</div>' }
const captainAmerica = { template: '<div>美国队长</div>' }
 

嵌套命名视图

官方在线示例:https://jsfiddle.net/posva/22wgksa3/

html部分:

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <h1>Nested Named Views</h1>
  <router-view></router-view>
</div>

js部分:

const UserSettingsNav = {
    template: `
<div class="us__nav">
  <router-link to="/settings/emails">emails</router-link>
  <br>
  <router-link to="/settings/profile">profile</router-link>
</div>
`
}
const UserSettings = {
    template: `
<div class="us">
  <h2>User Settings</h2>
  <UserSettingsNav/>
  <router-view class ="us__content"/>
  <router-view name="helper" class="us__content us__content--helper"/>
</div>
  `,
  components: { UserSettingsNav }
}

const UserEmailsSubscriptions = {
    template: `
<div>
    <h3>Email Subscriptions</h3>
</div>
  `
}

const UserProfile = {
    template: `
<div>
    <h3>Edit your profile</h3>
</div>
  `
}

const UserProfilePreview = {
    template: `
<div>
    <h3>Preview of your profile</h3>
</div>
  `
}

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/settings',
      // You could also have named views at tho top
      component: UserSettings,
      children: [{
          path: 'emails',
        component: UserEmailsSubscriptions
      }, {
          path: 'profile',
        components: {
            default: UserProfile,
          helper: UserProfilePreview
        }
      }]
    }
  ]
})

router.push('/settings/emails')

new Vue({
    router,
  el: '#app'
})

重定向和别名

重定向配置

重定向其实就是通过路由.拦截path,然后替换url跳转到redirect所指定的路由上. 重定向是通过 routes 配置来完成,

//从 /a 重定向到 /b
const router = new VueRouter({
    routes:[
        {path:'/a',redirect:'/b'}
    ]
})

///从 /a 重定向到 命名为'foo'的路由
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

//甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
      const { hash, params, query } = to
      //这里使用了ES6的解构写法,分别对应了to的hash模式,params,query参数.这里解构就不具体说明了.
        if (query.id === 'foo') {
          return { path: '/foo', query: null }
        }
        if (hash === '#baz') {
          return { name: 'baz', hash: '' }
        }
        if (params.id) {
          return '/with-params/:id'
        } else {
          return '/bar'
        }
    }}
  ]
})

别名

重定向是替url换路径,达到路由跳转.那别名就是一个路由有两个路径.两个路径都能跳转到该路由.
举个栗子:你可能大名叫'赵日天',但你的小名(别名)可能就叫'二狗子'.但'赵日天'和'二狗子'指代的是同一个人(路由).
别名是在rutes里的alias进行配置:

const router = new VueRouter({
//这时,路径'/fxxksky'和'/two-dogs' 都会跳转到A
  routes: [
    { path: '/fxxksky', component: A, alias: '/two-dogs' }
    //当有多个别名时,alias也可以写成数组形式.  alias: ['/two-dogs', 'three-dogs','four-dogs','five-dogs'] 
  ]
})

路由组件传参

路由传参,可以通过前面介绍的params和query进行传参.但这两种传参方式,本质上都是把参数放在url上,通过改变url进行的.这样就会造成参数和组件的高度耦合.

如果我想传参的时候,可以更自由,摆脱url的束缚.这时就可以使用rute的props进行解耦.提高组件的复用,同时不改变url.

下面就以例子进行讲解:

//路由配置:

const Hello = {
  props: ['name'], //使用rute的props传参的时候,对应的组件一定要添加props进行接收,否则根本拿不到传参
  template: '<div>Hello {{ $route.params}}和{{this.name}}</div>'
  //如果this.name有值,那么name已经成功成为组件的属性,传参成功
}

const router = new VueRouter({
mode: 'history',
  routes: [
    { path: '/', component: Hello }, // 没有传参  所以组件什么都拿不到
    { path: '/hello/:name', component: Hello, props: true }, //布尔模式: props 被设置为 true,此时route.params (即此处的name)将会被设置为组件属性。
    { path: '/static', component: Hello, props: { name: 'world' }}, // 对象模式: 此时就和params没什么关系了.此时的name将直接传给Hello组件.注意:此时的props需为静态!
    { path: '/dynamic/:years', component: Hello, props: dynamicPropsFn }, // 函数模式: 1,这个函数可以默认接受一个参数即当前路由对象.2,这个函数返回的是一个对象.3,在这个函数里你可以将静态值与路由相关值进行处理.
    { path: '/attrs', component: Hello, props: { name: 'attrs' }}
  ]
})

function dynamicPropsFn (route) {
  return {
    name: (new Date().getFullYear() + parseInt(route.params.years)) + '!'
  }
}

new Vue({
  router,
  el: '#app'
})

<!--html部分-->
    <div id="app">
      <h1>Route props</h1>
      <ul>
        <li><router-link to="/">/</router-link></li>
        <li><router-link to="/hello/you">/hello/you</router-link></li>
        <li><router-link to="/static">/static</router-link></li>
        <li><router-link to="/dynamic/1">/dynamic/1</router-link></li>
        <li><router-link to="/attrs">/attrs</router-link></li>
      </ul>
      <router-view></router-view>
    </div>

路由懒加载

vue主要用于单页面应用,此时webpack会打包大量文件,这样就会造成首页需要加载资源过多,首屏时间过长,给用户一种不太友好的体验.

如果使用路由懒加载,仅在你路由跳转的时候才加载相关页面.这样首页加载的东西少了,首屏时间也减少了.

vueRouter的懒加载主要是靠Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

routes:[
      path:'/',
      name:'HelloWorld',
      component:resolve=>require(['@/component/HelloWorld'],resolve)
  ]
  //此时HelloWorld组件则不需要在第一步import进来

把组件按组分块 (????????)

把组件按组分块可以把路由下的所有组件都打包在同个异步块 (chunk) 中,并且在f12的network里面看到动态加载的组件名字.
前提条件:

  • Webpack版本 > 2.4
  • 需要在webpack.base.conf.js里面的output里面的filename下面加上chunkFileName
output: {
 path: config.build.assetsRoot,
 filename: '[name].js',
 // 需要配置的地方
 chunkFilename: '[name].js',
 publicPath: process.env.NODE_ENV === 'production'
   ? config.build.assetsPublicPath
   : config.dev.assetsPublicPath
}

此时在引入组件时的写法需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

导航守卫

路由导航守卫,通俗点说就是路由钩子.作用也和生命周期钩子类似,在路由跳转过程进行操作控制.

导航守卫分类

全局守卫

异步执行,每个路由跳转都会按顺序执行.

  • router.beforeEach 全局前置守卫
  • router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用.
  • router.afterEach 全局后置钩子 进入路由之后 注意:不支持next(),只能写成这种形式

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由对象
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/')或者next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next传递任意位置对象,且允许设置诸如replace: true、name: 'home'之类的选项以及任何用在router-link的to prop或router.push中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
//1,可以在main.js 或者在单独的路由配置文件router.js中进行设置
    router.beforeEach((to, from, next) => { 
    ...
      next();
    });
    
//2,也可以在组件内部设置
    this.$router.beforeEach((to, from, next) => { 
    ...
      next();
    });
    
//3,对函数及next()的详细使用说明
    router.beforeEach((to, from, next) => { 
    //首先to和from 其实是一个路由对象,所以路由对象的属性都是可以获取到的(具体可以查看官方路由对象的api文档).
    //例如:我想获取获取to的完整路径就是to.path.获取to的子路由to.matched[0].
      next();//使用时,千万不能漏写next!!!
    //next()  表示直接进入下一个钩子.
    //next(false)  中断当前导航
    //next('/path路径')或者对象形式next({path:'/path路径'})  跳转到path路由地址
    //next({path:'/shotcat',name:'shotCat',replace:true,query:{logoin:true}...})  这种对象的写法,可以往里面添加很多.router-link 的 to prop 和 router.push 中的选项(具体可以查看api的官方文档)全都是可以添加进去的,再说明下,replace:true表示替换当前路由地址,常用于权限判断后的路由修改.
    //next(error)的用法,(需2.4.0+) 
    }).catch(()=>{
  //跳转失败页面
  next({ path: '/error', replace: true, query: { back: false }})
})
//如果你想跳转报错后,再回调做点其他的可以使用 router.onError()
router.onError(callback => { 
      console.log('出错了!', callback);
    });

路由独享的守卫

路由对象独享的守卫
beforeEnter:路由只独享这一个钩子,在rutes里配置

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // 使用方法和上面的beforeEach一毛一样
      }
    }
  ]
})

组件内的守卫

这类路由钩子是写在组件内部的

  • beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到this
  • beforeRouteUpdate (2.2) 路由复用同一个组件时
  • beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
//在组件内部进行配置,这里的函数用法也是和beforeEach一毛一样
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`
  }
}

 

完整的导航解析流程

路由元信息

一句话概括:路由配置的meta对象里的信息. 官方栗子:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

从栗子可以看出就是给路由添加了一个自定义的meta对象,并在里面设置了一个requiresAuth状态为true.

用处

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    //对matched不了解的建议看官方api文档,或我7.1节的说明
    //数组some方法,如果meta.requiresAuth为ture,则返回true.此时,说明进入该路由前需要判断用户是否已经登录 
    if (!auth.loggedIn()) {   //如果没登录,则跳转到登录页
      next({
        path: '/login',
        query: { redirect: to.fullPath }  //官方例子的这个小细节很好,通过query将要跳转的路由路径保存下来,待完成登录后,就可以直接获取该路径,直接跳转到登录前要去的路由
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

 

我们可以通过在meta里设置的状态,来判断是否需要进行登录验证.如果meta里的requiresAuth为true,则需要判断是否已经登录,没登录就跳转到登录页.如果已登录则继续跳转.
此时,可能会有同学说,前面说的path,params,query都可以存储信息,作为登录验证的状态标记.的确,它们也可以达到同样的效果.如果是少量单个的验证,使用它们问题不大.

但如果是多个路由都需要进行登录验证呢?path,params,query是把信息显性地存储在url上的.并且多个路径都把一个相同的状态信息加在url上.这样就使url不再单纯,并且也很不优雅美观.
所以要优雅要隐性地传递信息,就使用meta对象吧!

滚动行为

当你切换路由时,可以使页面滚动到你想要的某个地方,或者是保持之前滚动的位置,这时你就需要使用scrollBehavior这个方法.

  • 这里控制和记住的滚动位置都是仅对整个组件页面而言的,并不包含你组件里面其他的滚动条.
  • 这里路由的模式只能是history.因为它使用了History新增的pushState()
const router = new VueRouter({
mode:'history',//这个不能忘,默认是hash模式
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
     // to:要进入的目标路由对象,到哪里去.和导航守卫的beforeEach一样
     //from:离开的路由对象,哪里来
     //savedPosition: 点击前进/后退的时候记录值{x:?,y:?}.并且只有通过浏览器的前进后退才会触发.
    // return 期望滚动到哪个的位置 { x: number, y: number }或者是{ selector: string, offset? : { x: number, y: number }},这里selector接收字符串形式的hash,如'#foo',同时你还可以通过offset设置偏移,版本需要大于2.6+
    //举个实例
    if(savePosition) { //如果是浏览器的前进后退就,返回之前保存的位置
      return savePosition;
    }else if(to.hash) {//如果存在hash,就滚动到hash所在位置
      return {selector: to.hash}
    }else{
      return {x:0,y:0}//否则就滚动到顶部
    }
  }
})

特别感谢掘金id:shotCat前端大佬的文章,在本人Router的学习过程此文起到了关键作用。

最后修改:2021 年 07 月 28 日 12 : 16 AM