vue之vue-router

Vue-router(vue 路由)

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

一、认识路由

1.1 什么是路由?

  • 路由是网络工程里的专业术语
  • 路由(routing) 就是通过互联的网络把信息从源地址传输到目的地址的活动
  • 再来讲讲路由器?
    • 路由器提供两种机制:路由和传送
      • 路由是决定数据包从来源到目的地的路径
      • 传送石将输入端的数据转移到合适的输出端
  • 路由中有一个非常重要的概念叫:路由表
    • 路由表本质上就是一个映射表,决定了数据包的指向

补充:什么是前端渲染,什么是后端渲染?

1.2 后端路由

  • 网页发展的第一个阶段都是后端渲染。早期的网站开发整个HTML 页面是有服务器渲染的
  • 服务器直接生产渲染好的对应的HTML页面,返回给客户端进行展示
  • 但是一个网站,这么多页面服务器如何处理呢?
    • 一个页面有自己对应的网址,也就是URL
    • URL 会发送到服务器,服务器会通过正则对该URL进行匹配,并且最后交给一个Controller 进行处理
    • Controller 进行给中处理,最后生成HTML或者数据,返回给前端
    • 这就是完成了一个IO操作,这就是一个后端路由
  • 以上这种操作就是后端路由
    • 当我们页面中需要请求不同路径内容时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户端
    • 这种情况下渲染号的页面,不需要单独加载任何js和css,可以直接交给浏览器展示。这样也有利于SEO的优化。
  • 后端路由的缺点:
    • 一种情况是整个页面的模块有后端人员来编写和维护
    • 另一种请你赶快是前段开发人员如果想要开发页面,需要通过PHP和java等语言来编写页面代码
    • 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起,编写和维护都非常糟糕的事情

1.3 前后端分离

即后端只负责提供数据,不负责任何渲染的内容,渲染的任务交由前端渲染。

1.3.1前端渲染:

  • 浏览器中显示的网页中的大部分内容,都是由前端写js代码在浏览器中执行,最终渲染出来的网页。

  • 随着Ajax 的出现,有了前端分离的开发模式。

  • 后端只提供API 来返回数据,前端通过ajax 获取数据,并且可以通过javascript 将数据渲染到页面中

  • 这样做最大的优点就是前后端责任的清晰,后端专属于数据上,前端专注于交互和可视化

  • 并且当移动端(ios/android)出现后,后端不需要进行任何处理,依然使用之前额一套API即可。

  • 目前很多的网站依然采用这种开发模式

1.4 前端路由(单页面富应用阶段):

1.4.1 是什么?

  • 其实SPA (simple page web application)最主要的特点就是在前后端分离的基础上加上了一层前端路由
  • 也就是前端来维护一套路由规则,整个页面只有一个html页面
  • 对于vue 来说就是一个路由对应一个组件

1.4.2 实现的效果

  • 改变URL,但是页面不进行整体刷新
  • 如何实现呢?

1.4.3 URL 的hash

// hash 方式修改路由
location.hash = 'aaa'

// history 对象进行修改路由,底层类似堆栈的压栈。
history.pushState({},'','home')

// 以上两种方式不会刷新页面,但是会改变前段路由

// replace 是替换,不能返回
history.repalceState({},'','test')
// back 返回
history.back()
// go
history.pushState({},'','qzk')
// 弹出一个,相当于 history.back()
history.go(-1)

1.5 认识vue-router

  • 目前前段的三大主流框架,以及其路由

    • Angular 的ngRouter
    • React 的 ReactRouter
    • Vue 的 vue-router
  • vue-router 是Vue.js 官方的路由插件,他和vue.js 是深度集成的,适合用于构建单页面应用

  • 我们可以访问其官方网站对其进行学习:https://router.vuejs.org/zh/

  • vue-router 是基本的路由和组件的

    • 路由用于设定访问路径,将路径和组件映射起来
    • 在vue-router 的单页面应用中,页面的路径的改变就是组件的切换

1.6 安装和使用vue-router

步骤一、安装vue-router

npm install vue-router --save  # 使用 --save 应该这个vue在运行在客户端的时候也要使用
// project/src/router/index.js
// 配置路由相关的信息
// 导入Vue
import Vue from 'vue'
// 导入路由
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
// 使用步骤
// 第一步:通过 Vue.use() , 安装插件
// Vue.use(Router)

// 第二步: 创建路由对象
// const routes = [
//
// ]
// const router = new VueRouter({
//   routes
// })
// 第三部: 将 router 传入 vue实例中
// 导入 router 对象
// export  default router
// 最终就是下面的代码
export default new Router({
  // 通过 routes 配置映射关系
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})

// main.js
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})

总结:

  • 第一步:导入路由对象,并且调用Vue.use(VueRouter)
  • 第二步:创建路由实例,并且传入路由映射配置
  • 第三步:在vue实例中挂载创建的路由实例

二、vue-router 基本实用

  • 创建路由组件
  • 配置路由映射关系:组件和路由的映射关系
  • 会用路由,通过 router-link 和 router-view
  • 在前面的 router-link 标签中,我们知识使用了一个属性:to,用于指定跳转的路径
  • router-link 还有一些其他的属性:
    • tag 属性:指定 router-link 之后渲染成什么组件
    • replace 属性:replace 属性不会留下history 记录,所以指定replace的情况下,后退键返回不能返回到上一个页面
    • active-class 属性:当 router-link 对应的路由匹配成功时,会自动给当前元素设置一个router-link-active 的 class,设置active-class 可以修改默认属性的名称
      • 在进行高亮显示的导航菜单或者底部tabbar时,会使用到该类
      • 到时通常不会修改类的属性,会直接使用默认的router-link-active即可
<!-- 渲染成btn -->
<router-link to='/home' tag='button' replace active-class='active'></router-link>

也可以通过其他方式渲染

<template>
  <div id="app">
    <h1>我是APP哈哈</h1>
<!--    <router-link to="/home" tag="button">首页</router-link>-->
<!--    <router-link to="/about">关于</router-link>-->
    <button @click="homeClick">首页</button>
    <button @click="aboutClick">关于</button>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods:{
    homeClick(){
      // $.router 的push 方法 相当于 pushState,是可以返回的
      // this.$router.push('/home')
      // replace 是不可以返回的
      this.$router.replace('/home')
    },
    aboutClick(){
      // this.$router.push('/about')
      this.$router.replace('/about')
    }
  }
}
</script>

<style>
</style>

2.3 动态路由

  • 在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望如下路径:
    • /user/aaa 或者 /user/bbb
    • 除了有前面的/user 一样外,还有跟上用户id等信息
    • 这种path 和component的匹配关系,我们称之为动态路由(也就是路由传递数据的一种方式)
<!--user.vue 文件-->
<template>
  <div>
    <h2>用户页</h2>
    <p>用户个人信息</p>
    <!--方式一,通过计算属性获取-->
    <h3>{{userId}}</h3>
    <!--方式二,直接通过模板以及 $route.params 获取-->
    <h3>{{$route.params.userId}}</h3>

  </div>
</template>

<script>
  export default {
    name: "User",
    computed:{
      userId(){
        // 在vue 中 this.$router 指向的是vue router实例
        // this.$route 是指向处于当前活跃状态路由
        return this.$route.params.userId
      }
    }
  }
</script>

<style scoped>

</style>
// router/index.js
// 配置路由相关的信息
// 导入Vue
import Vue from 'vue'
// 导入路由
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
// 导入组件
import Home from "../components/Home";
import About from "../components/About";
import User from "../components/User";
// 使用步骤
// 第一步:通过 Vue.use() , 安装插件
Vue.use(Router)

// 第二步: 创建路由对象
const routes = [
  {
    path:'',
    redirect:'/home',
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  },
  {
    // 动态路由
    path:'/user/:userId',
    component:User
  }
]
const router = new Router({
  mode:'history',  // html5 的 history 模式
  routes
})
// 第三部: 将 router 传入 vue实例中
// 导入 router 对象
export default router

// 第四部:main.js 中使用router
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  // 使用router
  router,
  render: h => h(App)
})

三、vue-router 的懒加载:

  • 当打包构建应用的时候,Javascript 包会变得非常大,影响页面加载
  • 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

路由懒加载的方式:

  • 方式一:结合Vue的异步组件和webpack的代码分析
const Home = resolve => {require.ensure(['../componenets/Home.vue'],() => {
  resolve(requir('../components/Home.vue'))})};
  • 方式二:AMD写法
const About = resolve => require(['../components/About.vue'],resolve);
  • 方式三:在ES6中,我们可以更加简单的写法来组织Vue异步组件和Webpack的代码分割。
const Home = () => import('../components/Home.vue')

实现demo中的index,js

const Home = () => import(../components/Home)
Vue.use(VueRouter)
const routes =[
  {
    path:'',
    redirect:'/home',
  },
  {
    path:'/home',
    //component:() => import('../components/Home')
    component:Home
  }
]

注:一个路由懒加载,对应一个打包的js文件

四、vue-router 嵌套路由(实际开发中,路由嵌套是一个很常见的功能)

  • 比如在Home页面中,我们希望通过/home/news 和 /home/message访问一些内容
  • 一个路径映射一个组件,访问这两个路径也会分别渲染两个组件
  • 实现步骤:
    • 创建对应的子组件,并且在路由映射中配置对应的子路由
    • 在组件内部使用 router-view 标签
// 懒加载home父组件
const Home = () => import(../components/Home)
// 懒加载 home news 子组件
const News = () => import(../components/News)
Vue.use(VueRouter)
const routes =[
  {
    path:'',
    redirect:'/home',
  },
  {
    path:'/home',
    //component:() => import('../components/Home')
    component:Home,
    children:[
      // 基于重定向实现默认显示
      {
        path:'',
        redirect:'news'
      },
      {
        path:'news',
        component:News
      }
    ]
  }
]

注: 子路由是显示在父路由中的,所以我们还要在其父组件中,所以还要在父组件的vue文件中使用router-link&&router-view

五、vue-router 参数传递

vue-router 路由传参主要有两种类型:

  • params
    • 配置路由格式: /router/:id
    • 传递方式:在path(url)后跟上对应的值
    • 传递后形成的路径:/router/123,/router/abc
  • query
    • 配置路由路由格式:/router,也就是普通配置
    • 传递的方式:对象中使用 query的key作为传递方式
    • 传递后形成的路径:/router?id=123 , /router?id=abc

Demo example

App.vue

<template>
  <div id="app">
    <h1>我是APP哈哈</h1>
    <router-link to="/home" tag="button">首页</router-link>
    <router-link to="/about" tag="button">关于</router-link>
    <!--    <router-link :to="'/user/'+userId" tag="button">我的</router-link>-->
    <button @click="userClick">我的</button>
    <router-link to="/profile" tag="button">档案</router-link>

    <!--    <router-link :to="{path:'profileQuery',query:{'age':18,'name':'qzk'}}" tag="button">档案传参</router-link>-->
    <button @click="profileClick">档案传参</button>
    <!--    <button @click="homeClick">首页</button>-->

    <!--    <button @click="aboutClick">关于</button>-->
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App',
    data() {
      return {
        userId: 'qzk',
        age: 18,
        heigth: 180
      }
    },
    methods: {
      homeClick() {
        // $.router 的push 方法 相当于 pushState,是可以返回的
        // this.$router.push('/home')
        // replace 是不可以返回的
        this.$router.replace('/home')
      },
      aboutClick() {
        // this.$router.push('/about')
        this.$router.replace('/about')
      },
      userClick() {
        this.$router.push('/user/' + this.userId)
      },
      profileClick() {
        this.$router.push({path: 'profileQuery', query: {"age": 18, "name": "qzk"}})
      }

    }
  }
</script>

<style>
</style>

Router/index.js

// 配置路由相关的信息
// 导入Vue
import Vue from 'vue'
// 导入路由
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
// 导入组件
// 懒加载方式导入
const Home = () => import ("../components/Home")
const About = () => import('../components/About')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')
const ProfileQuery = () => import('../components/ProfileQuery')
// const UserInfo = () => import('../components/')
// import About from "../components/About";
// import User from "../components/User";
// import UserInfo from "../components/User";
// import login from "../components/login/login";
// 使用步骤
// 第一步:通过 Vue.use() , 安装插件
Vue.use(Router)

// 第二步: 创建路由对象
const routes = [
  {
    path: '',
    redirect: '/home',
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  },
  {
    path: '/user/:userId',
    component: User
  },
  // {
  //   path: '/userinfo',
  //   component: UserInfo
  // },
  {
    path: '/profile',
    component: Profile
  },
  {
    path:'/profileQuery',
    component:ProfileQuery
  }
]
const router = new Router({
  mode: 'history',  // html5 的 history 模式
  routes
})
// 第三部: 将 router 传入 vue实例中
// 导入 router 对象
export default router
// export default new Router({
//   // 通过 routes 配置映射关系
//   routes: [
//     {
//       path:'/home',
//       component: Home
//     },
//     {
//       path:'/about',
//       component:About
//     }
//   ]
// })

相关组件

About.vue

<template>
<div>
  <h1>我是关于</h1>
  <p>我是关于,哈哈哈</p>
</div>
</template>

<script>
  export default {
    name: "About"
  }
</script>

<style scoped>

</style>

Home.vue

<template>
  <div>
    <h1>我是首页</h1>
    <p>我是首页内容 ,哈哈哈</p>
  </div>
</template>

<script>
  export default {
    name: "Home"
  }
</script>

<style scoped>

</style>

Profile.vue

<template>
  <div>
    <h2>我是Profile 组件</h2>
  </div>
</template>

<script>
  export default {
    name: "Profile"
  }
</script>

<style scoped>

</style>

ProfileQuery.vue

<template>
  <div>
    <h2>我是ProfileQuery 组件</h2>
    <h3>{{$route.query}}</h3>
    <h3>{{$route.query.name}}</h3>
    <h3>{{$route.query.age}}</h3>
  </div>
</template>

<script>
  export default {
    name: "Profile"
  }
</script>

<style scoped>

</style>

User.vue

<template>
  <div>
    <h2>用户页</h2>
    <p>用户个人信息</p>
    <h3>{{userId}}</h3>
    <h3>{{$route.params.userId}}</h3>
  </div>
</template>

<script>
  export default {
    name: "User",
    computed:{
      userId(){
        // 在vue 中 this.$router 指向的是vue router实例
        // this.$route 是指向处于当前活跃状态路由
        return this.$route.params.userId
      }
    }
  }
</script>

<style scoped>

</style>

Main.js

import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  // 使用router
  router,
  render: h => h(App)
})

六、vue中$route 和 $router 的区别

  • $router 为VueRouter 实例,想要导航到不同的URL,则使用$router.push
  • $route 为当前router跳转对象里面可以获取name,path,query,params等
  • $router 是vue实例传入的router对象
  • $route 是当前活跃的route对象

七、vue-router 导航守卫

对前段路由来回跳转的过程的一个监听,即监听从哪里跳转到哪里

需求:跳转同事导航条上的title变更

我们在来分析一下需求:(如何改变网页的标题)

  • 网页标题是通过title 标签来显示的,但是SPA 只有一个固定的HTML ,切换不同的页面时,标题并不会改变
  • 但是我们可以通过js来修改title的内容,window.document.title = "新标题"
  • 那么在vue项目中,在哪里修改?什么时候修改比较合适:
    • 方式一:在组件的生命周期函数中修改mounted
      • 我们比较绒被冠以想到的修改标题的位置是每一个路由对应的组件.vue文件中
      • 通过mounted 生命周期函数,执行对应的代码进行修改
      • 但是当页面比较多的时候,这种方法不易维护(因为要在多个页面执行类似的代码)
    • 方式二:通过导航守卫,监听页面跳转,然后进行修改

什么是导航守卫?

  • vue-router 提供的导航守卫主要用来监听路由的进入和离开
  • vue-router 提供的beforeEach 和 afterEach 的钩子函数,踢门会在路由即将改变前和改变后触发

/router/index.js

// 配置路由相关的信息
// 导入Vue
import Vue from 'vue'
// 导入路由
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
// 导入组件
// 懒加载方式导入
const Home = () => import ("../components/Home")
const About = () => import('../components/About')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')
const ProfileQuery = () => import('../components/ProfileQuery')
// const UserInfo = () => import('../components/')
// import About from "../components/About";
// import User from "../components/User";
// import UserInfo from "../components/User";
// import login from "../components/login/login";
// 使用步骤
// 第一步:通过 Vue.use() , 安装插件
Vue.use(Router)

// 第二步: 创建路由对象
const routes = [
  {
    path: '',
    redirect: '/home',
  },
  {
    path: '/home',
    component: Home,
    meta:{
      title:'首页',
    }
  },
  {
    path: '/about',
    component: About,
    meta:{
      title:'关于',
    }
  },
  {
    path: '/user/:userId',
    component: User,
    meta:{
      title:'用户',
    },
  },
  // {
  //   path: '/userinfo',
  //   component: UserInfo
  // },
  {
    path: '/profile',
    component: Profile,
    meta:{
      title:'档案',
    }
  },
  {
    path:'/profileQuery',
    component:ProfileQuery,
    meta:{
      title:'档案2'

    },
  }
]
const router = new Router({
  mode: 'history',  // html5 的 history 模式
  routes
})
// 第三部: 将 router 传入 vue实例中
// 导入 router 对象
router.beforeEach((to,from,next) =>{
  // 从form 跳转到to
  document.title = to.matched[0].meta.title
  next()
})
export default router
// export default new Router({
//   // 通过 routes 配置映射关系
//   routes: [
//     {
//       path:'/home',
//       component: Home
//     },
//     {
//       path:'/about',
//       component:About
//     }
//   ]
// })

导航守卫补充

  • 补充一:如果是后置守卫,也就是afterEach, 不需要主动调用next() 函数
  • 补充二:上面我们使用的是导航守卫,被称之为全局守卫
    • 路由独享的守卫
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
  • 组件内的守卫
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`
  }
}

demo

Bad demo

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})

Good Demo

// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

八、keep-alive 和 vue-router(切换路由时,切换回来的时候保留原先的状态信息)

  • keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
    • Keep-alive 有两个非常重要的属性:
      • include — 字符串或正则表达,只有匹配的组件才会被缓存
      • exclude — 字符串或正则表达式,任何匹配的组件都不会被缓存
  • router-view 也是一个组件,如果直接被包在keep-alive 里面,所有的路由匹配到的视图组件都会被缓存
<template>
  <div>
    <h1>我是首页</h1>
    <p>我是首页内容 ,哈哈哈</p>
    <router-link :to="{name:'news'}" tag="button">新闻</router-link>
    <router-link :to="{name:'msgs'}" tag="button">档案</router-link>
    <keep-alive exclude='Profile,user'>
      <router-view></router-view>
    </keep-alive>
<!--    <router-view></router-view>-->
  </div>
</template>

<script>
  export default {
    name: "Home",
    data(){
      return {
        message:'你好',
        path:'/home/news'
      }
    },
    // 组件Vue实例被创建的时候调用
    created() {
      console.log('home created')
      // this.$router.push(this.path)
    },
    mounted() {
      console.log('home mounted')
    },
    // 组件实例被销毁的时候调用
    destroyed() {
      console.log('home destory')
    },
    // activated 和 deactivated 钩子函数只有在该组件被保持了状态使用了 keep-alive 时 才有效
    // 该函数只有在有keep-alive 时 钩子才生效
    activated() {
      console.log('home activated')
      this.$router.push(this.path)
    },
    //该函数只有在有keep-alive 时 钩子才生效
    deactivated() {
      console.log('home deactivated')
    },
    // 路由守卫
    beforeRouteLeave(to,from,next){
      console.log(this.$route.path)
      this.path = this.$route.path
      next()
    }
  }
</script>

<style scoped>

</style>
原文地址:https://www.cnblogs.com/qianzhengkai/p/13227328.html