教你如何使用vue-router

1,298 阅读6分钟

一、安装vue-router

脚手架安装:npm install vue-routeroryarn add vue-router

二、创建组件

src->router->index.js:这个文件创建路由的核心文件:

import Vue from 'vue' // 先引入vue文件
import Router from 'vue-router' // 引入vue-router 文件
Vue.use(Router)  // 全局注册路由

import Home from '../components/router-demo/home.vue'
import About from '../components/router-demo/about.vue'
import User from '../components/router-demo/user'
import Name from '../components/router-demo/name'

export default new Router({ // 创建路由实例
    routes:[{   // 配置路由信息
        path:'/home', // 路径
        name:'home', // 路径name
        component:Home // 对应组件
    },{
        path:'/about', // 路径
        name:'about', // 路径name
        // 当前路径下的子路由信息
        children: [{
            path:'user',
            name:'user',
            component:User
        },{
            path:'name',
            name:'name'
            component:Name
        }]
    }]
})
// app.vue
<template>
    <div>
        <router-link to="/home">Home</router-link>
        <router-link to="/about">About</router-link>
        <router-view></router-view>
    </div
</template>
// 子路由配置about.vue
<template>
    <div>
      <router-link to="/about/user">user</router-link> 
      <router-link to="/about/name">name</router-link> 
      <router-view></router-view>
    </div>
</template>

三、页面导航/页面显示

1、router-link
默认会被渲染成带有链接的标签,可有通过to来指定跳转的组件
被选中router-link将自动添加一个class属性值router-link-active

to:导航的路径,要填写router/index.js中配置path值。

2、router-view
用于渲染匹配到的组件,同时页面显示。

    1. 如果想要有过度效果可以在router-view外层包裹transition
    1. keep-live可以缓存数据,这样不至于重新渲染路由组件时候,之前哪个路由组件被清楚。
<template>
    <transition>  
        <keep-alive>
            <router-view></router-view>
        </keep-alive>
    </transition>
</template>

四、动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件,调用routermap方法映射路由,每条路由是以key=>value的形式存在,key是路径,value是组件。

例:User组件下,对于ID不同的用户,都使用这个组件来渲染,那么,可以在vue-router的路径中使用[动态路由参数]来达到这个效果。

const router = new VueRouter({
  routes: [{
    // 动态路径参数 以冒号开头
    path: '/user/:id',
    component: User 
    }]
})
export default router

另外一种:'user/foo'和'user/bar' 都映射到相同的路由,参数值可以通过$route.params.id访问

<template>
    <div>
        <p>user: {{$routes.params.id}}</p>
    </div>
</template>
**or**
// 使用路由守卫
<template> '...'</template>
<script>
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
</script>

在一个路由中设置多段『路径参数』,对应的值都会设置到 $route.params 中。例如:

提醒一下,当使用路由参数时,例如从/user/foo导航到/user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象

<template>...</template>
<script>
    watch:{
        '$route'(to,from){
            // 对路由作出响应
        }
    }
</script>

五、vue-router参数传递

vue-router传递大致分为两类

  • 编程式传参:router.push
  • 声明式传参:<router-link/>

声明式传参:

  1. <router-link to = "/user">

  2. router-link标签to传参

    <router-link :to = "{ name:'eric',params:{ key: value }}">跳转链接</router-link>
    // 访问参数:{{$route.params.key}}
    
    // 同时router/index.js中
    path:'/home/:id',
    

    name: 就是我们路由配置中起的name值,命名式路由就是用一个名称来标示路由,在定义路由时候设置一个name属性即可,
    params: 就是我们要传递的参数,它也是对象的形式,在对象可以传递多个值。
    $route.params.key进行接收

  3. <router-link :to="{ path:'user', query: { userID: 53 }}"> 跳转到详情页 </router-link>

    访问参数:{{$route.query.userID}}

编程式传参:

  1. this.$router.push("detail")detail为要跳转的路由地址,该方式简单但无法传递参数。
  2. this.$router.push({ name:"detail",params:{personId:53}}):detail为要跳转的路由地址,params为传递的参数,目标页面可以使用this.$router.params.personId来获取传递的参数,该方式有一个缺点就是在目标页面刷新时传递过来的参数会丢失。
  3. this.$router.push({path:"/detail",query:{personId:53}})detail为要跳转的路由地址,query为传递的参数,目标页面使用this.$router.query.personId来获取传递的参数,该方式会把传递的参数放在url上,如:localhost:8080/#/detail/?personId=53

url传参:

(:) 冒号的形式传递参数

    const router = new VueRouter({
      routes: [{
        // 动态路径参数 以冒号开头
        path: '/params/:id/:userName',
        component: User 
        }]
    })
    export default router
    // 访问url传递的参数
    <template>
        <div>
            <h2>{{ msg }}</h2>
            <p>新闻ID:{{ $route.params.id}</p>
            <p>用户名:{{ $route.params.userName}}</p>
        </div>
    </template>
    <script>
        export default {
          name: 'params',
          data () {
            return {
              msg: 'params page'
            }
          }
        }
    </script>
query和params区别
  • query: 相当于get请求,页面跳转,地址栏中会看到请求参数。
  • params:相当于post请求,页面跳转,地址蓝中不会看到请求参数。

六、重定向

刚进入应用都是进入到“/”这个路由的,如果想直接进入到“/goods”怎么办。
有两种方法。一种是利用重定向,另一种是利用vue-router的导航式编程。

    // 一种
    const routes = [{ 
        path: '/', 
        // 重定向
        redirect: '/goods'
    }]
    // 重定向的目标也可以是一个命名的路由。
    const routes = [{ 
        path: '/', 
        redirect: { 
            name: 'goods' 
        }
    }]
    // 重定向时一个方法
    {
      path:'/',
      redirect:to => {
          // 方法接收 目标路由 作为参数
        // return 重定向的 字符串路径/路径对象
      }
    }
  • alias:别名的使用
    • 首先我们在路由配置文件里给路径起一个别名:'eric'
    • { path: '/hi', component: Hi, alias:'/eric' }
  • 配置我们的,起过别名之后,可以直接使用标签里的to属性,进行重新定向。
    • router-link to="/eric">jspang</router-link>

区别:

  • redirect:仔细观察URL,redirect是直接改变了url的值,把url变成了真实的path路径。
  • alias:URL路径没有别改变,这种情况更友好,让用户知道自己访问的路径,只是改变了<router-view>中的内容(⭐️⭐️⭐️⭐️⭐️别名aliaspath为'/'中,是不起作用的)

七、vue-router导航守卫

当做vue-cli项目的时候感觉路由跳转前做一些验证,比如:登陆验证,是网站的普遍需求。对此,vue-router提供的beforeEach可以方便实现全局导航守卫。

如何设置全局守卫:

可以使用router-beforeEach注册一个全局前置守卫:

const router = new VueRouter({...})
router.beforeEach((to,from,next)=>{
    next();
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve完之前一直处于等待中

  • 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() 注册过的回调。

确保要调用next方法,否则钩子就不会被resolved

const router = new VueRouter({ ... }) //路由配置

const whiteList = ['/error', '/register/regindex', '/register/userauthent',  '/register/submit'] 
// 路由白名单
router.beforeEach((to,from,next) => {
    console.log("进入守卫
    
    if (userInfo.user_id>0){
        console.log("登录成功");
        next();   //记得当所有程序执行完毕后要进行next(),不然是无法继续进行的;
    }else{
        console.log("登录失败");
        getUserInfo.then(res => {
            if(res){
                if (res.user_id){
                    if (res.status == 4) {
                        //账号冻结
                        next({ path: '/error', replace: true, query: { noGoBack: true } })
                    }
                    if (res.status == 3) {
                        //认证审核中
                        next({ path: '/register/submit', replace: true, query: { noGoBack: true } })
                    }
                    if (res.status != 1 && res.status != 3) {
                        if (!res.mobile ) {
                            next({ path: '/register/regindex', replace: true, query: { noGoBack: true }})
                        } else {
                            //绑定完手机号了
                            next({ path: '/register/userauthent', replace: true, query: { noGoBack: true } })
                        }
                    }
                    next();  //记得当所有程序执行完毕后要进行next(),不然是无法继续进行的;
                }else{
                    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
                        next();  //记得当所有程序执行完毕后要进行next(),不然是无法继续进行的;
                    }else{
                        next({ path: '/register/regindex', replace: true, query: { noGoBack: true }})
                    } 
                }
            }else{
                }
            }
        }).catch(()=>{
            //跳转失败页面
            next({ path: '/error', replace: true, query: { noGoBack: true }})
        })
    }
});

export default router

最后和大家说下如果白名单太多或项目更大时,我们需要把白名单换为vue-router路由元信息: 直接在路由配置的时候,给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。用它来做登录校验再合适不过了。

// 路由信息配置
    {
      path: '/actile',
      name: 'Actile',
      component: Actile,
      meta: {
        login_require: false
      },
    },
    {
      path: '/goodslist',
      name: 'goodslist',
      component: Goodslist,
      meta: {
        login_require: true
      },
      children:[
        {
          path: 'online',
          component: GoodslistOnline
        }
      ]
    }

这里我们只需要判断item下面的meta对象中的login_require是不是true,就可以做一些限制了

    router.beforeEach((to, from, next) => {
      if (to.matched.some(function (item) {
        return item.meta.login_require
      })) {
        next('/login')
      } else 
        next()
    })

全局解析守卫

你可以用router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用

路由独享守卫

  1. 路由配置文件中的钩子函数

beforeEnter钩子,进入该路由时执行的操作:

// router/index.js
 {
     path:'/params/:userName',
      component:Params,
      // 路由信息钩子
      beforeEnter:(to,from,next)=>{
        console.log('我进入了params模板');
        console.log(to);
        console.log(from);
        next();
}
  1. 组件内的守卫

beforeRouteEnter:在路由进入前的钩子函数。
beforeRouteLeave:在路由离开前的钩子函数。
beforeRouteUpdate:在路由更新是的够子函数

    //.vue 文件
    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 => {
    // 通过 `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)
  }
}

导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的created钩子中获取数据。这让我们有机会在数据获取期间展示一个loading状态,还可以在不同视图间展示不同的loading状态。假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据

<template>
  <div class="post">
    <div class="loading" v-if="loading">
      Loading...
    </div>
    <div v-if="error" class="error">
      {{ error }}
    </div>
    <div v-if="post" class="content">
      <h2>{{ post.title }}</h2>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
export default {
  data () {
    return {
      loading: false,
      post: null,
      error: null
    }
  },
  created () {
    // 组件创建完后获取数据,
    // 此时 data 已经被 observed 了
    this.fetchData()
  },
  watch: {
    // 如果路由有变化,会再次执行该方法
    '$route': 'fetchData'
  },
  methods: {
    fetchData () {
      this.error = this.post = null
      this.loading = true
      // replace getPost with your data fetching util / API wrapper
      getPost(this.$route.params.id, (err, post) => {
        this.loading = false
        if (err) {
          this.error = err.toString()
        } else {
          this.post = post
        }
      })
    }
  }
}

导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后再执行导航。 通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法。

export default {
  data () {
    return {
      post: null,
      error: null
    }
  },
  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  // 路由改变前,组件就已经渲染完了
  // 逻辑稍稍不同
  beforeRouteUpdate (to, from, next) {
    this.post = null
    getPost(to.params.id, (err, post) => {
      this.setData(err, post)
      next()
    })
  },
  methods: {
    setData (err, post) {
      if (err) {
        this.error = err.toString()
      } else {
        this.post = post
      }
    }
  }
}

八、History模式

vue-router 默认hash模式 - 当 URL改变是,页面不会重新加载。

vue-router 设置history模式 - 充分利用history.pushState API来完成URL跳转而无须重新加载页面。

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

mode两个值
histroy:当你使用 history模式时,URL就像正常的url,http://www.dxl.com/user/id不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://www.dxl.com/user/id就会返回404。所以要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。 这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面

const router = new VueRouter({
  mode: 'history',
  routes: [
  // 这里的path:'*'就是找不到页面时的配置,component是我们新建的一个Error.vue的文件
    { path: '*', component: Error}
  ]
})

hash:默认’hash’值,但是hash看起来就像无意义的字符排列,不太好看也不符合我们一般的网址浏览习惯。不配置时是这样的:http://localhost:8080/#/users,多个#号。

九、数据获取

有时候,进入某个路由后,需要从服务器获取数据。

  • 导航完成后获取数据:先完成导航,然后在接下来的组件生命周期钩子中获取函数。在数据获取件显示“显示加载中”之类的指示。
  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

导航完成后获取数据:


使用这种方式,马上会导航和渲染组件,然后在组件的create钩子中获取数据。这时候我们有机会在数据获取期间展示一个loading状态,还可以在不同视图间展示不同的loading状态。

<template>
   <div>
       <div v-if="loading" class="loading">
           loading....
       </div>
       <div v-if= "error" class="error">
       {{ error }}
       </div>
       <div v-if="post" class= "content">
           <h3>{{post.title}}</h3>
           <h3>{{post.body}}</h3>
       </div>
   </div>
</template>
<script>
   export default {
       data(){
           return{
               loading: false,
               post:null,
               error:null
           }
       },
       created(){
           // 调用方法
           this.fetchData()
       },
       watch:{
           // 监听路由有变化,再次执行该方法
           '$route':'fetchData'
       },
       methods:{
           // 获取数据 设置状态
           fetchData(){
               this.error = this.post = null
               this.loading = true
               // 请求数据
               getPost(this.$route.params.id,(err,post)=>{
                   this.loading = false
                   if(err){
                       this.error = err.toString()
                   }else{
                       this.post = post
                   }
               })
           }
       }
   }
</script>

导航完成前获取数据:


通过这种方式,在导航赚到新的路由前获取数据。

 <template>
   <div>
       <div v-if="loading" class="loading">
           loading....
       </div>
       <div v-if= "error" class="error">
       {{ error }}
       </div>
       <div v-if="post" class= "content">
           <h3>{{post.title}}</h3>
           <h3>{{post.body}}</h3>
       </div>
   </div>
</template>
<script>
export default {
   data(){
       return{
           post:null,
           error:null
       }
   },
   // 
   beforeRouteEnter (to, from, next) {
       getPost(to.params.id, (err, post) => {
         next(vm => vm.setData(err, post))
       })
   },
 // 路由改变前,组件就已经渲染完了
 // 逻辑稍稍不同
 beforeRouteUpdate (to, from, next) {
   this.post = null
   getPost(to.params.id, (err, post) => {
     this.setData(err, post)
     next()
   })
 },
 methods: {
     // 判断数据获取又没有错误
   setData (err, post) {
     if (err) {
       this.error = err.toString()
     } else {
       this.post = post
     }
   }
 }
}
}
   
</script>