zoukankan      html  css  js  c++  java
  • VueJs(12)---vue-router(导航守卫,路由元信息,获取数据)

    vue-router(导航守卫,路由元信息,获取数据)

            之前泄露两篇有关vue-router博客:

           VueJs(10)---vue-router(进阶1)

           VueJs(11)---vue-router(进阶2)

    一、导航守卫

            当做Vue-cli项目的时候感觉在路由跳转前做一些验证,比如登录验证,是网站中的普遍需求,这个时候就需要导航守卫,你可以在页面跳转前做逻辑判断,时候跳转,跳转到指定页面。

           (1)全局的(beforeEach,afterEach,beforeResolve),

           (2)单个路由独享的(beforeEnter),

           (3)组件级的(beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave)。

    1、全局守卫

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

    const router = new VueRouter({ ... })
    
    router.beforeEach((to, from, next) => {
      // 业务逻辑处
    })

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

    • to: Route: 即将要进入的目标 路由对象

    • from: Route: 当前导航正要离开的路由

    • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

       next(): 进行管道中的下一个钩子。这个是必须要的,否在无法完成跳转。

       next(false): 中断当前的导航。就相当于点击之后不会跳转到指定页面,就相当于没有写next()一样。

       next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,

       next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

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

    全局后置钩子

        你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

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

    路由独享的守卫

       你可以在路由配置上直接定义 beforeEnter 守卫

    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })

    2、组件内的守卫

    最后,你可以在路由组件内直接定义以下路由导航守卫

    • 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 => {
        // 通过 `vm` 访问组件实例
      })
    }

       注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

    beforeRouteUpdate (to, from, next) {
      // just use `this`
      this.name = to.params.name
      next()
    }

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

    beforeRouteLeave (to, from , next) {
      const answer = window.confirm('你确定要退出吗')
      if (answer) {
        next()
      } else {
        next(false)
      }
    }

    下面用一个小案例说明:

    <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">
        <button type="button" @click="fooClick">/user/foo</button>
        <button type="button" @click="profileClick">/user/foo/profile</button>
        <button type="button" @click="postsClick">/user/foo/posts</button>
    
        <router-view></router-view>
    </div>
    <script>
        //定义了三个组件
        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/foo',
                    component: UserHome,
                    beforeEnter: (to, from, next) => {
                        console.log('进入UserHome组件');
                        next();
                    }
                }, {
                    path: '/user/foo/profile',
                    component: UserProfile,
                    beforeEnter: (to, from, next) => {
                        console.log('进入profile组件');
                        next();
                    }
                }, {
                    path: '/user/foo/posts',
                    component: UserPosts,
                    beforeEnter: (to, from, next) => {
                        console.log('UserPosts组件');
                        //这里设置flase说明就算路径为'/user/foo/posts',也不会进入UserPosts组件
                        next(false);
                    }
                }]
            })
            //设置全局导航守卫    
        router.beforeEach((to, from, next) => {
            console.log('进入全局守卫导航');
            console.log('进入路由路径:' + to.path);
            console.log('离开路由路径:' + from.path);
            next();
        })
        const app = new Vue({
            el: '#app',
            router,
            data: {
                foo: '/user/foo',
                profile: '/user/foo/profile',
                posts: '/user/foo/posts'
            },
            methods: {
                fooClick: function(event) {
                    this.$router.push({
                        path: this.foo
                    });
                },
                profileClick: function(event) {
                    this.$router.push({
                        path: this.profile
                    });
                },
                postsClick: function(event) {
                    this.$router.push({
                        path: this.posts
                    });
                } }    
        })
    </Script>

    效果:

    通过上面可以得出以下结论:

         1、全局守卫会比组件守卫先执行

         2.全局守卫第一次加载页面to和from路径都是“/”

         3.每一次路径改变都会调用全局组件,路径不变是不会调用全局守卫,也不会在调用组件守卫

         4.我在/user/foo/posts路径设置为next(flase)是,当点击它是并没有跳转UserPosts组件,页面显示的还是profile说明跳转失败。

    二、路由元信息(meta)

        为什么会有路由元信息这个东西?首先要知道它到底有什么作用。

       我们在做网站登录验证的时候,可以使用到beforeEach 钩子函数进行验证操作,如下面代码 ,如果页面path为’/goodsList’,那么就让它跳转到登录页面,实现了验证登录。

    router.beforeEach((to, from, next) => {
      if (to.path === '/goodsList') {
        next('/login')
      } else 
        next()
    })

     如果需要登录验证的网页多了怎么办?

    1.这里是对比path。如果需要验证的网页很多,那么在if条件里得写下所有的路由地址,将会是非常麻烦的一件事情。

    2.因为路由是可以嵌套的。有’/goodsList’,那么可能会有’/goodsList/online’,再或者还有’/goodsList/offline’’/goodsList/audit’、’/goodsList/online/edit’等等。

    如果像刚才例子中这样对比(to.path === ‘/goodsList’),就非常单一,其他的路径压根不会限制(验证)到,照样能正常登陆!因为每个to.path根本不一样。

    我们所理想的就是把’/goodsList’限制了,其他所有的以’/goodsList’开头的那些页面都给限制到!

    to Route: 即将要进入的目标 路由对象 
    我们打印一下to

    它有很多属性,有 
      - fullPath 
      - hash 
      - matched 
      - meta 
      - name 
      - params 
      - path 
      - query

    其中有个属性,matched,就是匹配了的路由,我们打印出来,这个是个数组。它的第一项就是{path: “/goodslist”},一直到最为具体的当前path (例如:{path: “/goodslist/online/edit”})

    这里可以循环matched这个数组,看每一项的path 有没有等于’/goodsList’,只要其中一个有,那么就让它跳转到登录状态

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

    那么这里只是对goodsList进行验证判断,且限制的还是path,就相当于把goodsList下面都给限制了,但有些东西是不需要登陆直接可以显示在页面的,比如:资讯信息,广告信息。这么做不就不可行了。用path来做限制似乎有点不好用。轮到主角登场了

    meta字段(元数据)

    直接在路由配置的时候,给每个路由添加一个自定义的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()
    })

         meta还可以放其它信息,比如可以存储该路由相关信息(例如:设置每个路由的title,取路由的title设置为选项卡的标题)

    {
          path: '/router2',
          name: 'router2',
          component:router2,
          meta:{
            title:"积分模块"
          }
        }
    // 全局前置守卫
    router.beforeEach((to,from,next) => {
        console.log(to);
        console.log(from);
        if(to.meta.title) {
          document.title = to.meta.title;
        } else {
          document.title = '我是默认的title'
        }
        next();
    });

    三、获取数据

    有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。

    我们可以通过两种方式来实现:

    (1)导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。

    (2)导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

    1、导航完成后获取数据

          当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 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
            }
          })
        }
      }
    }
    (err, post) =>是ES6写法,意思大概就是function(err, post){}大括号就相当于方法类逻辑。

    2、在导航完成前获取数据

    通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 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
          }
        }
      }
    }

    在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。

    想太多,做太少,中间的落差就是烦恼。想没有烦恼,要么别想,要么多做。上尉【1】

  • 相关阅读:
    07 总结ProgressDialog 异步任务
    1. vim 的安装及配置
    debian 源设置 ( apt-get 不能安装)
    在Debian中安装VNC Server
    让BB-Black通过usb0上网
    常用的一些 linux 指令
    Linux下同一目录内文件和目录为什么不能同名?
    beaglebone black 与电脑互传文件(夹)
    永久修改 putty字体大小
    Beaglebone Black的引脚分配
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/9016792.html
Copyright © 2011-2022 走看看