Vue.js(五) 路由(vue-router)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/vbirdbest/article/details/85216551

官方文档:https://router.vuejs.org/zh/installation.html

一:简介

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

路由:就是“页面”之间跳转,因Vue.js是单页应用(single page application,SPA),即组件之间的切换。

二: 声明式路由< router-link to="/path" >

路由开发步骤:

  1. 开发组件(component)
  2. 配置路由(routes)
  3. 渲染:路由链接(< router-link to="/path">)和路由视图(< router-view>< /router-view>)

将组件 (component) 映射到路由 (routes),然后告诉 Vue Router哪个链接(< router-link to="/path">) 要在哪里(< router-view>)来渲染它们。

2.1 Foo.vue

<template>
    <div>
      Foo
    </div>
</template>

<script>
  export default {
    name: 'Foo'
  }
</script>

<style scoped>

</style>

2.2 Bar.vue

$route.params.路径变量: 用于获取路由的路径变量, 就像Java中的@PathVariable一样。监听动态路径参数的变化可以通过watch: { '$route' (to, from) { } }或者beforeRouteUpdate (to, from, next) { }, 两者的区别是watch没有next 参数。

watch: {
  '$route' (to, from) {
    // 对路由变化作出响应...
  }
},
beforeRouteUpdate (to, from, next) {
	next()
}
<template>
    <div>
      Bar {{ this.$route.params.id }}
    </div>
</template>

<script>
  export default {
    name: 'Bar',
    created () {
      this.init()
    },
    beforeRouteUpdate (to, from, next) {
      console.log(to)
      console.log(from)
      console.log(next)
      // 需要调用next()才会继续路由,就像拦截器一样放开拦截
      next()
      this.init()
    },
    methods: {
      init () {
        console.log('Bar created ' + this.$route.params.id)
      }
    }
  }
</script>

<style scoped>

</style>

2.3 配置路由(src/router/index.js)

关于path,可以使用路径参数(:参数名)的形式,也可以使用更加复杂的正则表达式。这种称之为“动态路由”
使用表达式当匹配到多个路径时优先匹配第一个满足条件的路径。

注意:当多个路径路由到同一个组件时,因为组件会被复用,所以此时对于生命周期钩子只会调用一次。可以通过beforeRouteUpdate函数来监听路由的变化,在函数体内处理一些逻辑,比如将created的逻辑提取到某个方法中,然后在created和beforeRouteUpdate分别调用该方法。

// 导入vue-router,vue-router需要依赖vue
import Vue from 'vue'
import Router from 'vue-router'

import Foo from '../components/Foo'
import Bar from '../components/Bar'

// 使用vue-router插件
Vue.use(Router)

// 导出Router对象,以便被其它文件(main.js)导入
export default new Router({
  routes: [
    {
      path: '/foo',
      component: Foo
    },
    {
      path: '/bar/:id',
      name: 'bar',
      component: Bar
    }
  ]
})

2.4 src/mian.js

import Vue from 'vue'
import App from './App'
// 导入Router
import router from './router'

/* eslint-disable no-new */
// 引用router
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

2.5 App.vue

  • 使用<router-link to="/path">配置页面跳转链接,<router-link to="/path">将被解析成<a href="#/path" class="router-link-active">如果路径匹配成功后还会自动设置class=“router-link-active”。
  • <router-view>用于定义跳转页面的模板内容将在哪个地方展示,即跳转页面的内容将会把<router-view>标签替换成具体的模板内容
<template>
  <div id="app">
    Vue Router
    <p>
      <router-link to="/foo">Go to Foo</router-link>
      <router-link to="/bar/1">Go to Bar 1</router-link>
      <router-link :to="{name: 'bar', params: {id: 2}}">Go to Bar 2</router-link>
    </p>
    
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App'
  }
</script>

<style>
  #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
  }
</style>

在这里插入图片描述

三:编程式路由this.$router.push(…)

3.1 this.$router.push(…)

使用<router-link>标签来切换组件称之为声明式路由。也可以通过编程的方式来调用路由器router的push方法来切换组件, 调用router.push(...)方法会向 history栈 添加一个新的记录,所以当用户点击浏览器后退按钮时,则回到之前的URL。实际上声明式路由内部也会调用router.push(…)方法。

// 添加组件(会将路径添加到history栈中)
router.push(location, onComplete?, onAbort?)

// 替换组件(不会将路径添加到history栈中)
router.replace(location, onComplete?, onAbort?)

// 前进或者后退
router.go(n)
  • location : 切换组件的路径,可以是一个纯字符串的路径,或者是一个路径对象(可以包含组件名称name、路径path、参数params、查询参数query)。

  • onComplete:可选回调函数,将会在导航成功完成 (在所有的异步钩子被解析之后)后回调。

  • onAbort:可选回调函数,终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由)时调用。

    // 字符串
    router.push('foo')
    
    // 对象
    router.push({ path: 'foo' })
    
    // 命名的路由(注意:params不能与path结合使用)
    // name:是指都会配置路由时的name值
    // 这种方式参数不会追加在路径后面
    router.push({ name: 'user', params: { userId: 123 }})
    
    // 带查询参数,变成 /register?plan=private
    // 这种方式会将参数追加到路径后面
    router.push({ path: 'register', query: { plan: 'private' }})
    

router.replace跟router.push很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

3.2 router.go(n)

这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

3.3 使用示例

3.3.1 Foo.vue

<template>
    <div>
      Foo
    </div>
</template>

<script>
  export default {
    name: 'Foo'
  }
</script>

<style scoped>

</style>

3.3.2 Bar.vue

<template>
    <div>
      Bar query: {{ this.$route.query }}
    </div>
</template>

<script>
  export default {
    name: 'Bar',
    created () {
      this.init()
    },
    beforeRouteUpdate (to, from, next) {
      console.log(to)
      console.log(from)
      console.log(next)
      // 需要调用next()
      next()
      this.init()
    },
    methods: {
      init () {
        console.log('Bar created ' + this.$route.params.id)
      }
    }
  }
</script>

<style scoped>

</style>

3.3.3 Foobar.vue

<template>
    <div>
      Foobar {{ this.$route.params.id }}
    </div>
</template>

<script>
  export default {
    name: 'Foobar'
  }
</script>

<style scoped>

</style>

3.3.4 src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foobar from '../components/Foobar'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/foo',
      component: Foo
    },
    {
      path: '/bar/:id',
      component: Bar
    },
    {
      path: '/foobar',
      name: 'Foobar',
      component: Foobar
    }
  ]
})

3.3.5 App.vue

<template>
  <div id="app">
    Vue Router
    <p>
      <button @click="jumpFoo">字符串foo</button> <br>
      <button @click="$router.push({path: 'foo'})">对象{path: 'foo'}</button> <br>
      <button @click="$router.push({name: 'Foobar', params:{id: 1}})">命名的路由 {name: '', params: {id: 1}}"</button> <br>
      <button @click="$router.push({path: '/bar/666', query:{age: 2}})">带查询参数<{path: '', query: {}}</button> <br>

      <button @click="$router.go(-1)">后退一步</button> <br>
    </p>

    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App',
    methods: {
      jumpFoo() {
        this.$router.push('foo', function () {
          console.log('onComplete: 跳转完成')
        }, function (err) {
          console.log('onAbort')
        })
      }
    }
  }
</script>

<style>
  #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
  }
</style>

在这里插入图片描述

四:导航守卫

导航守卫(导航的生命周期):用于监听路由发生变化的各个生命周期。可以在多个地方来监听路由的变化,如全局的, 单个路由独享的, 或者组件级的。

  • router.beforeEach(to, from, next) => { }) 全局前置守卫
    • to : Route类型,即将要进入的目标路由对象。
    • from: Route类型,当前导航正要离开的路由对象。
    • next:Function类型,用于决定接下来如何导航,如进行下一个钩子、 中断当前导航、跳转到其它地址等。确保要调用 next 方法,否则钩子就不会被 resolved。
      • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
      • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
      • next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’、redirect 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
      • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
  • beforeEnter: (to, from, next) => { } 路由独享的守卫, 配置在路由中
  • beforeRouteEnter(to, from, next) { } 组件内的守卫,在组件内部使用,在渲染该组件的对应路由被 confirm 前调用
  • beforeRouteUpdate (to, from, next) { } 组件内的守卫,在组件内部使用,在当前路由改变,但是该组件被复用时调用,举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
  • beforeRouteLeave (to, from, next) { } 组件内的守卫,在组件内部使用,导航离开该组件的对应路由时调用。

完整的导航解析流程:

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

使用示例

App.vue

<template>
  <div id="app">
    <button @click="$router.push('/foo')">foo</button> <br>
    <button @click="$router.push('/bar')">bar</button> <br>

    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App',
  }
</script>

src/main.js 全局监听路由变化

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'


Vue.config.productionTip = false

Vue.use(ElementUI)

router.beforeEach((to, from, next) => {
  console.log('全局前置守卫beforeEach')
  console.log(to)
  console.log(from)
  console.log('------------------')
  next()
})

router.afterEach((to, from) => {
  console.log('全局后置钩子afterEach')
  console.log(to)
  console.log(from)
  console.log('------------------')
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

src/router/index.js 单个路由的导航监听

import Vue from 'vue'
import Router from 'vue-router'

import Foo from '../components/Foo'
import Bar from '../components/Bar'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        console.log('路由独享的守卫beforeEnter')
        console.log(to)
        console.log(from)
        console.log('------------------')
        next()
      }
    },
    {
      path: '/bar',
      component: Bar
    }
  ]
})

Foo.vue 组件内部导航路由的监听

<template>
    <div>
      Foo 路由守卫
    </div>
</template>

<script>
  export default {
    name: 'Foo',
    beforeRouteEnter (to, from, next) {
      console.log('beforeRouteEnter')
      console.log(to)
      console.log(from)
      console.log('------------------')
      next()
    },
    beforeRouteUpdate (to, from, next) {
      console.log('beforeRouteUpdate')
      console.log(to)
      console.log(from)
      console.log('------------------')
      next()
    },
    beforeRouteLeave (to, from, next) {
      const answer = window.confirm('你确定要离开吗?')
      if (answer) {
        console.log('beforeRouteLeave')
        console.log(to)
        console.log(from)
        console.log('------------------')
        next()
      } else {
        next(false)
      }
    }
  }
</script>

在这里插入图片描述

meta(路由元信息)

meta用于配置路由的元数据,可以理解为为该路由配置一些静态参数,然后在获取到目标路由时获取这些参数。

比如:为每个路由配置是否需要登录才能访问,有的路由需要登录,有的路由不需要登录,可以配置一个全局导航,判断一下目标路由是否需要登录,如果需要登录再检查是否已经登录,如果没有登录就跳转到登录界面,否则跳转到目标组件。

App.vue

<template>
  <div id="app">
    <button @click="$router.push('/foo')">foo</button> <br>
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App',
  }
</script>

src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

import Foo from '../components/Foo'
import Login from '../components/Login'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/foo',
      component: Foo,
      meta: { requiresAuth: true }
    },
    {
      path: '/login',
      component: Login
    }
  ]
})

src/main.js

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    let isNotLogin = true;
    if (isNotLogin) {
      next({
        path: '/login',
        query: { redirect: to.fullPath}
      })
    } else {
      next()
    }
  } else {
    next()
  }
})

五:参数转属性

组件在this.$router.push(...)时可以携带参数(动态路径、params、query),我们在目标组件中可以通过{{ this.$route.params.参数名 }}或者{{ this.$route.query }}来获取参数, 我们也可以将参数转换成属性props, 然后直接使用 {{ 参数名 }}来获取。

props可以为布尔类型或者函数类型,布尔类型一般用于动态路径,函数类型更加灵活强大。在配置路由时需要配置props, 在目标组件也要定义出所有可用的props。

App.vue

<template>
  <div id="app">
    <button @click="$router.push('/foo/2')">动态路径转属性</button> <br>
    <button @click="$router.push({name: 'Foobar', params:{nickname: 'mengday', status: 1}})">params转属性</button> <br>
    <button @click="$router.push({path: '/bar/666', query:{username: 'mengday', age: 2}})">query转属性</button> <br>
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App',
  }
</script>

Foo.vue

<template>
    <div>
      Foo <br>
      this.$route.params.id={{ this.$route.params.id }}<br>
      {id} = {{id}}
    </div>
</template>

<script>
  export default {
    name: 'Foo',
    props: ['id']
  }
</script>

Bar.vue

<template>
    <div>
      Bar {{ this.$route.params.id }} <br>
      {id} = {{id}} <br>
      {username} = {{username}} <br>
      {age} = {{age}} <br>
      {baz} = {{baz}} <br>
    </div>
</template>

<script>
  export default {
    name: 'Bar',
    props: ['id', 'username', 'age', 'baz']
  }
</script>

Foobar.vue

<template>
    <div>
      Foobar <br>
      {nickname} = {{ nickname }} <br>
      {status} = {{ status }} <br>
    </div>
</template>

<script>
  export default {
    name: 'Foobar',
    props: ['nickname', 'status']
  }
</script>

src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foobar from '../components/Foobar'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/foo/:id',
      component: Foo,
      props: true
    },
    {
      path: '/bar/:id',
      component: Bar,
      props: (route) => ({
        id: route.params.id,
        username: route.query.username,
        age: route.query.age,
        baz: 'baz'
      })
    },
    {
      path: '/foobar',
      name: 'Foobar',
      component: Foobar,
      props: (route) => ({
        nickname: route.params.nickname,
        status: route.params.status
      })
    }
  ]
})

在这里插入图片描述

六:重定向与别名

“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b。

别名:就是为一个组件的path另起一个别名,path和alias访问任意一个路径都能路由到对应的组件。

src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foo2 from '../components/Foo2'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/index',
      redirect: '/foo'
    },
    {
      path: '/index2',
      redirect: {name: 'Bar'}
    },
    {
      path: '/foo',
      component: Foo
    },
    {
      path: '/bar',
      name: 'Bar',
      component: Bar
    },
    {
      path: '/foo2',
      alias: '/foo22',
      component: Foo2
    }
  ]
})

App.vue

<template>
  <div id="app">
    <button @click="$router.push('/index')">redirect: '/foo'</button> <br>
    <button @click="$router.push('/index2')">redirect: {name: Bar}</button> <br>
    <button @click="$router.push('/foo2')"> foo2 </button> <br>
    <button @click="$router.push('/foo22')"> alias </button> <br>
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App'
  }
</script>

在这里插入图片描述

七:组件嵌套

一个单页面应用是一个根组件(App.vue),根组件中会嵌套多个子组件,子组件中又会嵌套多个孙子组件。总之一个单页面应用是由无数个子组件嵌套而成,就像一棵倒立的树结构一样。

在这里插入图片描述

在上一个示例的基础上,写一个嵌套结构的组件。

3.1 Foo1.vue

<template>
    <div>
      Foo1
    </div>
</template>

<script>
  export default {
    name: 'Foo1'
  }
</script>

<style scoped>

</style>

3.2 Foo2.vue

<template>
    <div>
      Foo2
    </div>
</template>

<script>
  export default {
    name: 'Foo2'
  }
</script>

<style scoped>

</style>

3.3 src/router/index.js

组件可以通过children属性来配置子组件。

import Vue from 'vue'
import Router from 'vue-router'

import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foo1 from '../components/Foo1'
import Foo2 from '../components/Foo2'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'foo1',
          component: Foo1
        },
        {
          path: 'foo2',
          component: Foo2
        }
      ]
    },
    {
      path: '/bar/:id',
      component: Bar
    }
  ]
})

3.4 Foo.vue

<template>
    <div>
      Foo
      <p>
        <router-link to="/foo/foo1">Go to Foo1</router-link>
        <router-link to="/foo/foo2">Go to Foo2</router-link>
      </p>
      <router-view></router-view>
    </div>
</template>

<script>
  export default {
    name: 'Foo'
  }
</script>

<style scoped>

</style>

在这里插入图片描述

八:命名视图

命名视图就是给<router-link>设置一个name属性,name属性的值就是在配置路由时指定的name值,设置了name意思就是该视图只用于展示name对应的组件内容,而不会展示别的组件的内容。

当在同级展示多个视图(< router-view >),而不是嵌套里面。一个视图(< router-view >)对应一个组件(component), 多个视图就对应多个组件。在配置路由时使用components来配置多个组件,同时给每个组件起一个名称。在使用视图时通过名称来指定该视图对应的组件。

Top.vue

<template>
    <div>
      Top
    </div>
</template>

<script>
  export default {
    name: 'Top'
  }
</script>

<style scoped>

</style>

Left.vue

<template>
    <div>
      Left
    </div>
</template>

<script>
  export default {
    name: 'Left'
  }
</script>

<style scoped>

</style>

Main.vue

<template>
    <div>
      Main
    </div>
</template>

<script>
  export default {
    name: 'Main'
  }
</script>

<style scoped>

</style>

src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

import Top from '../components/Top'
import Left from '../components/Left'
import Main from '../components/Main'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      components: {
        top: Top,
        left: Left,
        main: Main
      }
    }
  ]
})

App.vue

<template>
  <div id="app">
    <router-view class="top" name="top"></router-view>
    <router-view class="left" name="left"></router-view>
    <router-view class="main" name="main"></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App'
  }
</script>

<style>
  .top {
    float: left;
    width: 100%;
    height: 100px;
    background-color: orange;
  }
  .left {
    float: left;
    width: 25%;
    height: 700px;
    background-color: #2c3e50;
  }
  .main {
    float: left;
    width: 75%;
    height: 700px;
    background-color: dimgray;
  }
</style>

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/85216551