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】

  • 相关阅读:
    bzoj 1017 魔兽地图DotR
    poj 1322 chocolate
    bzoj 1045 糖果传递
    poj 3067 japan
    timus 1109 Conference(二分图匹配)
    URAL 1205 By the Underground or by Foot?(SPFA)
    URAL 1242 Werewolf(DFS)
    timus 1033 Labyrinth(BFS)
    URAL 1208 Legendary Teams Contest(DFS)
    URAL 1930 Ivan's Car(BFS)
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/9016792.html
Copyright © 2011-2022 走看看