vue导航守卫

导航守卫

VueRouter提供的导航守卫主要用于在导航的过程中重定向或取消路由、或者添加权限验证、数据获取等业务逻辑。

导航守卫分为三类:全局守卫、路由独享守卫和组件内守卫。可以用于路由导航过程中的不同阶段。

每一个导航守卫都有三个参数:to、from和next。

  • to:表示即将进入的目标路由对象
  • from:当前导航正要离开的路由对象
  • next:函数,以下是next的常用方法
    • next(): 进行管道中的下一个钩子
    • next(false): 中断当前导航
    • next( ./xx ): 中断当前导航,并跳转至设置好的路径

1.全局守卫

全局守卫分为全局前置守卫、全局解析守卫和全局后置钩子。

1.1 全局前置守卫

当一个导航触发时,全局前置守卫按照创建的顺序调用。守卫可以是异步解析执行,此时导航在所有守卫解析完之前一直处于挂起状态。全局前置守卫使用router.beforeEach()注册。

const router = new VueRouter({....});
router.beforeEach((to,from,next)=>{
    //这里执行具体操作
    //next 调用
})

在使用全局前置守卫时,要确保next函数的正确调用。例如,下面就是一个错误的示例。

router.beforeEach((to,from,next)=>{
    if(!localStorage.getItem('token'))next('/login');
    //如果用户没有验证,next函数被调用两次
    next();
})

正确的做法是:

router.beforeEach((to,from,next)=>{
    if(!localStorage.getItem('token'))next('/login');
    //如果用户没有验证,next函数被调用两次
    else next();
})

1.2 全局解析守卫

全局解析守卫是vue-router2.5.0版本新增的,使用【router.beforeResolve】注册。它和【router.beforeEach】类似,区域在于,在导航被确认之前,在所有组件内守卫和异步路由组件被解析之后,解析守卫被调用。

const router = new VueRouter({....});
router.beforeResolve((to,from,next)=>{
    //这里执行具体操作
    //next 调用
})

1.3 全局后置钩子

全局后置钩子使用【router.afterEach】注册,它在导航被确认之后调用。

const router = new VueRouter({....});
router.afterEach((to,from)=>{
    //这里执行具体操作
})

1.4 案例:登录验证

对于受保护的资源,我们需要用户登录后才能访问。如果用户没有登录,那么就将用户导航到登录页面。为此,可以利用全局前置守卫来完成用户登录与否的判断。

在components目录下新建login.vue。

<template>
    <div>
        <h3>{{ info }}</h3>
        <table>
            <caption>用户登录</caption>
            <tbody>
                <tr>
                    <td><label for="username">用户名:</label></td>
                    <td><input id="username" v-model.trim="username" placeholder="请输入用户名"/></td>
                </tr>
                <tr>
                    <td><label for="password">密码:</label></td>
                    <td><input id="password" v-model.trim="password" type="password" placeholder="请输入密码"/></td>
                </tr>
                <tr>
                    <td cols="2">
                        <input type="submit" value="登录" @click.prevent="login"/>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</template>
<script>
export default {
    data(){
        return {
            username: "",
            password: "",
            info: ""   // 用于保存登录失败后的提示信息
        }
    },
    methods: {
        login() {
            // 实际场景中,这里应该通过Ajax请求上服务端去验证
            if("lisi" == this.username && "1234" == this.password){
                // sessionStorage中存储的都是字符串值,
                // 因此这里实际存储的将是字符串"true"
                sessionStorage.setItem("isAuth", true);
                this.info = "";
                //如果存在查询参数
                if(this.$route.query.redirect){   
                    let redirect = this.$route.query.redirect;
                    //跳转至进入登录页前的路由
                    this.$router.replace(redirect); 
                }else{
                    // 否则跳转至首页
                    this.$router.replace('/');    
                }
            }else{
                sessionStorage.setItem("isAuth", false);
                this.username = "";
                this.password = "";
                this.info = "用户名或密码错误";
            }
                
        }
    }
}
</script>

修改路由配置文件index.js。这里只显示新增代码

...
import Login from '@/components/Login'
...

// 将VueRouter实例作为模块的默认导出
const router = new VueRouter({
  mode: 'history',
  routes: [
    ...
    {
      path: '/login',
      name: 'login',
      component: Login,
      meta: {
        title: '登录'
      }
    }
  ]
});
router.beforeEach((to, from, next) => {
  // 判断目标路由是否是/login,如果是,直接调用next()方法
  if(to.path == '/login'){
    next();
  }else{
    // 否则判断用户是否已经登录,注意这里是字符串判断
    if(sessionStorage.isAuth === "true"){
      next();
    } 
    // 如果用户访问的是受保护的资源,且没有登录,则跳转到登录页面,
    // 并将当前路由的完整路径作为查询参数传给Login组件,以便登录成功后返回先前的页面
    else{
      next({
        path: '/login',
        query: {redirect: to.fullPath}
      });
    }
  }
})

export default router;

要注意的是,代码中的 对路由是否是login的判断不能缺少,否则会导致死循环。例如:初次访问news,此时用户还没有登录,条件判断为false,跳转到login,然后又执行全局前置守卫,条件判断依然为false,再次跳转到login,最后导致栈溢出。

为了方便访问登录页面,可以在app.vue中新增一个登录的导航链接。

<router-link :to="{ name: 'login'}">登录</router-link>

完成上述修改后,运行项目。出现登录页面后,输入正确的用户名(lisi)和密码(1234),看看路由的跳转,之后输入错误的用户名和密码,再看看路由的跳转。

1.5 案例:页面标题

在单页面应用程序中,实际只有一个页面,因此在页面跳转时,标题不会发生改变。

在定义路由时,在routes配置中的每个路由对象(也称为路由记录)都可以使用一个meta字段,来为路由对象提供一个元数据信息。我们可以为每一个组件在他的路由记录里添加meta字段,在该字段中设置页面的标题,然后在全局后置钩子中设置目标路由页面的标题。全局后置钩子是在导航确认后,DOM更新前调用,因此在这个钩子中设置页面标题是比较合适的。

修改index.js中的代码

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/news',
      name: 'news',
      component: News, 
      meta: {
        title: '新闻'
      }
    },
    ....
     //下面类似,为每一个路由对象添加一个meta字段。
  ]
})
...
router.afterEach((to, from) => {
  document.title = to.meta.title;
})

meta字段也可以用于对有限资源的保护,在需要保护的路由对象中添加一个需要验证属性,然后在全局前置守卫中进行判断,如果访问的是受保护的资源,继续判断用户是否已经登录,如果没有,则跳转到登录页面。

{
    path: '/videos',
    name: 'videos',
    component: Videos,
    meta: {
       title: '视频',
       requiresAuth: true
    }
}

在全局前置守卫中进行判断。

router.beforeEach((to, from, next) => {
  // 判断该路由是否需要登录权限
  // 路由对象的matched属性是一个数组,包含了当前路由的所有嵌套路径片段的路由记录。
  if (to.matched.some(record => record.meta.requiresAuth))
  {
    // 路由需要验证,判断用户是否已经登录
    if(sessionStorage.isAuth === "true"){
      next(); 
    }
    else{
      next({
        path: '/login',
        query: {redirect: to.fullPath}
      });
    }
  }else{
    next();
  }
})

1.6 matched

这里说明一下为什么要使用遍历to.matched数组判断meta的requiresAuth字段,而不直接使用to.meta.requiresAuth来判断。

我们需要知道的前提是以下两点:

  • vue路由匹配时会同时匹配满足情况的所有路由,即如果路由是‘/cinema/plan’的话,‘/cinema’也会触发。
  • 另外如果较高等级的路由需要登录控制的话,它所有的嵌套路由基本也需要登录控制。

先来看一个嵌套路由的例子

routes:
[
   {
      path: '/cinema',
      redirect: '/page/cinema',
      component: BlankLayout,
      meta: { title: '影院' , requiresAuth: true}
      children: [
        {
          path: '/cinema/plan',
          name: 'cinemaPlan',
          component: () => import('./views/cinema/Plan'),
          meta: { title: '影院排期' }
        },
        {
          path: '/cinema/cinemaDetail',
          name: 'cinemaDetail',
          component: () => import('./views/cinema/CinemaDetail'),
          meta: { title: '影院详情' }
        }
       ]
    }
]

假设两种情况:

(1)cinema具有登录控制,而cinemaPlan 没有。如果用户正常点击路由跳转的话,它必然是先进一级路由,再去二级路由,一级路由实现登录控制,利用to.meta是能够满足的,注意这里是用户正常点击,但是假如有用户直接改变url地址的话去访问cinemaPlan的话,则需要给cinemaPlan路由添加requiresAuth字段,同理也需要给cinemaDetail添加字段,如果路由比较多的话,就会很麻烦。

(2)cinema没有登录控制,而cinemaPlan有。这种情况确实不怕用户直接改变url访问二级路由了,但是同样如果过多二级路由,也是需要设置许多requiresAuth。

所以,为了方便,直接遍历to.matched数组,该数组中保存着匹配到的所有路由信息。就该例而言,访问cinema时,matched数组长度为1,访问cinemaPlan时,matched数组长度为2,即保存着‘/cinema’以及‘/cinema/plan’。其实啰嗦了这么多,直接使用to.meta判断字段也可以,就是需要给所有需要控制的路由添加requiresAuth。而to.matched则只需要给较高一级的路由添加requiresAuth即可,其下的所有子路由不必添加。

2.路由独享守卫

路由独享守卫是在routes配置的路由对象中直接定义的beforeEnter守卫。

const router = new VueRouter({
  routes: [
    {
      path: '/news',
      name: 'news',
      component: News, 
      meta: {
        title: '新闻'
      },
       beforeEnter:(to,from,next)=>{
           //....
       }
    },
    ....
     //下面类似,为每一个路由对象添加一个meta字段。
  ]
})

beforeEnter守卫只在该组件上生效,在全局前置守卫调用之后,在进入路由组件之前调用。

3.组件内守卫

共有三个组件内守卫:beforeRouteEnter、beforeRouterUpdate和beforeRouteLeave。

//
const book = {
    template:"...",
    beforeRouteEnter (to, from, next) {
       //在渲染该组件的路由被确认之前调用
       //不能通过this来访问组件实例,因为在守卫执行前,组件实例还没有被创建
    },
    beforeRouteUpdate (to, from, next) {
        //在渲染该组件的路由改变,但是该组件被复用时调用
        //例如,对于一个带有动态参数的路由 /foo/:id,在/foo/1和/foo/2之间跳转的时候
        //相同的foo组件实例将会被复用,而这个守卫就会在这种情况下被调用。
        //可以访问组件实例的this
    },
    beforeRouteLeave (to, from, next) {
       //导航即将离开该组件的路由时调用
       //可以访问组件实例的this
    }
}

beforeRouterEnter守卫不能访问this,因为在守卫是在导航确认前被调用,这时新进入的组件实例还没有被创建。

不过beforeRouteEnter有一个特权,就是它的next函数支持回调,而其他的守卫则不行。可以把组件实例作为回调方式的参数,在导航被确认后执行回调,而这个时候,组件实例已经创建完成。利用这个特性,可以将created钩子用beforeRoute守卫来替换。

<template>
    <div>
        <p>图书ID:{{ book.id }}</p>
        <p>书名:{{ book.title }}</p>
        <p>说明:{{ book.desc }}</p>
    </div>
</template>

<script>
import Books from '@/assets/books'
export default {
    data(){
        return {
            book: {}
        }
    },
    methods: {
        setBook(book){
            this.book = book;
        }
    },
    /* created(){
        this.book = Books.find((item) => item.id == this.$route.params.id);
    }, */

    beforeRouteEnter (to, from, next) {
        let book = Books.find((item) => item.id == to.params.id);
        next(vm => vm.setBook(book));
    },
    //替换监听
    beforeRouteUpdate (to, from, next) {
        this.book = null;      
        this.book = Books.find((item) => item.id == to.params.id);
        next();
    }
}
</script>

assets目录下的books代码如下:

export default [
   {id: 1, title: 'Vue.js无难事', desc: '前端框架经典图书'},
   {id: 2, title: 'VC++深入详解', desc: '畅销10多年的图书'},
   {id: 3, title: 'Servlet/JSP深入详解', desc: '经典JSP图书'}
]

beforeRouteLeave守卫通常用来防止用户在还未保存修改前突然离开,可以通过next(false)来取消导航。

beforeRouteLeave(to,from,next){
    const answer = confirm('真的要离开吗?您还未保存已修改的内容!')
    if(answer){
        next()
    }else{
        next(false);
    }
}
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值