7、Vue2(二) vueRouter3+axios+Vuex3

14.vue-router 3.x

路由安装的时候不是必须的,可以等到使用的时候再装,如果之前没有安装的话,可以再单独安装。之前的终端命令行不要关闭,再重新开一个,还需要再package.json文件的依赖中添加。

如果忘记之前是否有安装过,可以在package.json文件中查看依赖是否有router。

vue是单页面应用(SPA,在一个页面中切换)。

路由:简单来说就是路径跟视图的对应关系(一个路径对应一个视图)。vue中的切换都是基于路由去切换的。

安装

npm install vue-router
//安装指定版本
npm install vue-router@3.2.0

npm i jquery --save(缩写-S)生产依赖 --save-dev(缩写-D)开发依赖  将jquery安装并保存到生产依赖或开发依赖中
而现在不用这么麻烦了,直接npm install jquery 直接自动保存到生产依赖中,如果需要保存到开发依赖中还是需要像之前一样

卸载:npm uninstall jquery

使用步骤

在使用组件名之前都需要引入组件所在的文件。

在 router 文件夹下的 index.js 文件中:

1.引入vue、 vue-router import Vue from 'vue' import VueRouter from 'vue-router'

2.注册组件VueRouter,Vue.use(VueRouter) (因为这是为vue量身打造的,只要不是vue的核心并且是为vue量身定做的才可以注册。如vue-router和vuex)

3.定义路由配置项(哪个路径对应哪个视图)。------重点。①路由的核心就是路径与视图的对应关系:使用路由懒加载的方式引入。②路由代表的不是整个页面,而是需要变动的某一块页面,需要在最大的组件App.vue中给路由视图留出位置。<router-view></router-view>

//在router文件下的index.js文件中使用组件名之前是要引入组件
import MyHome from '../views/MyHome'
import MyAbout from '../views/MyAbout'

//定义路由配置项:路径和组件视图
const routes=[
    {
        path:'/',
        component:'MyHome'
    },
    {
        path:'/about',
        component:'MyAbout'
    }
]
<!-- 在App.vue文件中合适的位置留出路由视图的位置 -->
<router-view></router-view>

component: 组件名 在文件顶部引入组件文件

4.创建 VueRouter 实例并导出,然后 在main.js的根组件中导入,传入配置项,挂载到实例上。

实战中使用路由的步骤(只是需要自己修改的部分,每个路由文件都需要的步骤不包括,如果是面试题还是上面的步骤):

1、在路由文件夹下的index.js文件下引入需要的组件文件;

或者一开始不引入,使用懒加载(建议采用该方法)

2、定义路由配置项:路径和视图的对应关系。

3、在App.vue文件中留出合适的路由视图渲染的位置。<router-view></router-view>

const routes=[
{
  path:'/',
  component:组件名1
  //使用懒加载的方法
  //component:()=>import("路径")
},
{
  path:'/about',
  component:组件名2
},....
]
//使用案例:router文件夹下的index.js文件
import Vue from 'vue' //引入vue
import VueRouter from 'vue-router'  //引入vue-router
//import MyHome from '../views/MyHome' //1 引入组件或使用路由懒加载
//import MyAbout from '../views/MyAbout'

Vue.use(VueRouter)//注册组件,给vue定制的,只要不是vue本身的东西使用的时候都得注册

//2 定义路由配置项:路径和视图的对应关系
const routes = [
  {
    path: '/',   //默认路径开始显示的页面
    // component: MyHome //组件名,前提是引入组件
    //使用路由懒加载
    component:()=>import('../views/MyHome.vue')

  },
  {
    path: '/about',//路径不可重复,以 / 开头
    //component:MyAbout
    //使用路由懒加载
    component:()=>import('../views/MyAbout')
  }
]

//创建一个路由实例,导出去,在根组件main.js中引入,根配置项中才能使用路由
const router = new VueRouter({
  routes
})
export default router
<!-- app.vue文件 -->
<template>
    <div>
        <h1>{{msg}}</h1>

        <my-info></my-info>  
        <router-view></router-view>  <!-- 3 路由视图就渲染在此-->
    </div>

</template>

<script>
    import MyInfo from './views/MyInfo.vue' //引入的组件 
    export default {
        data(){
          return {
            msg:"我是App根组件"
          }
        },
        components:{MyInfo}
    }
</script>

<style scoped>
    h1{
      color: blue;
    }
</style>

路由的两个核心

(1)定义路由配置项

(2)定义路由视图的显示位置(router-view)

路径定义规则:路径以斜线打头

页面与路由一定是一一对应的,一个页面一定要对应路由,否则找不到

**路由的懒加载:**之前是先把组件导入,不管用不用都把组件加载了,那些可能用不上的组件就会发生性能浪费。使用路由的懒加载的话就先不加载组件,等访问哪个页面再去加载哪个页面对应的路由。

component: () => import('../views/About.vue')
//只有访问该页面时才会调用该函数,加载

关于路径的写法

import Abc from "./demo"    

1.如果当前目录下有demo文件夹,那么是去引入的demo文件夹下的index.js
2.如果不存在demo文件夹,会优先去找当前目录下的demo.vue文件
3.如果不存在.vue文件,那么会优先查找当前目录下的demo.js文件

路径中@代表的是src目录,本质上是因为webpack内部配置的

嵌套路由

在整个项目系统中一进来就已经分了两个一级路由了:登陆注册页面和主页面,如果是没有登录注册就显示登录注册页面,如果登陆过了就显示主页面,这两个页面中显示一个。

二级路由是点击两边的导航而切换的页面。

注意点:每一级路由都必须要有该级路由的router-view。

1.哪个组件还有子路由,那么需要在该组件内部定义router-view,代表该组件的子路由显示在该位置上

2.子路由的配置,定义在父路由的children属性下,配置内容同父路由

子路由如果不加斜线,那么默认在上级路由上叠加,如果加了斜线,那么前面必须带上 上级路由。

//配置路由项
const routes=[
    {
        path:'/home',
        name:"Home",//name属性叫什么都可以,但是不能重名,在路由跳转的时候会用到
        component:()=>import("@/views/MyHome"),
        children:[{
            path:'/home/about',
            name:"About",
            component:()=>import("@/views/MyAbout")
        }]
    },
    {
        path:'/login',
         name:"Login",
         component:()=>import("@views/MyLogin")
    }
]


//2 在父组件MyHome中找到路由试图router-view的位置。

路由的跳转

需要路由配置项的name属性

1.js形式的跳转:当用户请求成功/事件时的自动跳转

如果看到了某个方法是以$打头的,基本可以断定,该方法是Vue根实例的方法。

this.$router.push('字符串路径 /home/about ') //this指向的是当前组件
this.$router.push({ path: '/home/about' })
this.$router.push({ name: 'About'})

2.标签形式的跳转:当用户点击时的跳转情况,类似html中的a标签

<router-link to="/home/about">跳转 </router-link>  
// 1.to 可以是字符串路径     还可以是对象(但需要是js的执行环境,加':')   2.:to="{path:'路径'}"   3.{name:"名字"}
//路由嵌套少的话使用路径较好,但是如果是嵌套情况较多的话建议是使用name跳转

3.浏览器的跳转:前提是浏览器要有记录

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

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

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

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

动态路由

多个路由匹配同一个页面,动态不写死。例如教务系统中查询成绩时的 www.qinghua. search / 20220101和www. qinghua. search/20220102,前半部分一样,都是查询页面。

使用场景:多个不同的路径匹配同样的页面。(也能够实现跨组件传递数据,拿到动态路径的名字id,但是这样的话就会出现必须传值的情况。)

动态路由匹配 在路径后面加/:名字。这样在输入路径的时候名字可以是任意的,他们都对应的是同一个页面。

如果想要拿到地址的名字内容,在另一个页面中使用的话,使用$route.params.名字(至于前面加不加this,如果是在html中写的则不加,如果是在js的methods中写的则要加)即可。

this.$router拿到的是根路由实例,this.$route拿到的是当前路由实例。

如:

{ path: '/users/:id', component: User },
 //可以同时存在多个动态路由   
{ path: '/users/:id/:no', component: User },

注意只改后面动态路径参数是不会重新触发组件生命周期的。

但是如果页面中用到了动态路由的数据,那么切换动态路径的时候,是会触发组件更新的生命周期beforeUpdate和updated的(视图需要重新渲染)。

当有动态路径发生改变,想要在动态路径改变时做事情,组件生命周期是不会发生变化的,(只有页面中用到了动态路由的数据时才会发生变化,这样就很不稳定),但是使用导航守卫中的组件内的守卫钩子beforeRouterUpdaete是能够检测到的,然后在里面写我们需要做的事情就行了。

动态路由的跳转

1.this.$router.push({ name: 'user', params: { userId: '123' }}) //使用这种方法很方便,动态路径可以将params的值设置为变量
2.this.$router.push({ path: '/user/123' })//如果用这个的话还不如用下面的那个
2.this.$router.push( '/user/123')
//下面的写法是错误的,path和parmas不能一起写,一起写会导致不知道哪一个是动态路径了
router.push({ path: '/user', params: { userId: '123' }})//如果提供了 path,params 会被忽略

同样的规则适用于router-link标签

查询参数

不需要使用动态路径的。

使用场景:用于跨组件传递数据。(实现页面跳转并且把数据带过去在另一个页面中使用)

查询参数的跳转的时候直接在地址后面拼接?key=value,不用像动态路由那样修改路由配置项。

router.push({ path: '/register', query: { plan: 'private' }})
router.push({ path: '/register?key=value'}})
router.push("/register?key=value&key2=value2")

接收数据的页面使用该数据时:this.$route.query.key

注意 name和params配套

     path和query 配套(因为路径后面没有冲突,直接就是数据)

动态路由和查询参数的相同点:都能实现跨页面传递数据;

不同点:

1.跳转方式:动态路由 name params;查询参数 path query.

2.动态路由更多的用于多个路径匹配同一个页面中;查询参数更多用于跨组件传递数据。

而且使用查询参数不需要在路由中定义东西的,不需要改路由配置项,而动态路由需要先改路由表/:id

3.动态路由传递参数不方便,不管用不用都必须传值。

注意:query查询参数传递的数据不要是引用数据类型的数据,只能传基本类型的数据,如果非要想传对象,可以将其转为字符串类型的数据。

捕获所有路由

访问的路由不存在,匹配不上时,显示的是404页面时使用。

{
  // 会匹配所有路径
  path: '*', 
  component:()=>import("@/views/NotFound")
}
{
  // 会匹配以 `/user-` 开头的任意路径
  path: '/user-*'
}

当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后

新版本不限制 通配符的存放位置,也就是说就算是放在最开始,其他路由也是能匹配上的,不过不推荐

组件路由对象

在组件中通过this.$route拿到当前组件的路由信息

{name:"组件的名字",fullPath:"完整路径",path:"路径",matched:[匹配上的所有路由,一级路由匹配上的是一个,嵌套路由匹配上的是多个],meta:"路由元信息",params:"动态路径参数",query:"查询参数"}

重定向和别名

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

在路由配置项中添加redirect:"要跳转的路径".

const routes =[
  {
    path:'/home',
    name:"Home",
    redirect:'/login', //重定向:加了这个一进入home页面就会自动跳转到login页面,URL发生了改变
    alias:'/abc', //别名:URL没有发生改变,通过这两个名字都能访问到 使用的时候很少
    component:()=>import ('@/views/MyHome'),
    children:[
      {
        path:'about',
        name:'About',
        component:()=>import('@/views/MyAbout')
      }
    ]
  },
]

重定向使用场景:如淘宝在未登录时购买物品一直会跳转 到登录页面。// 或者 对于管理系统而言,一级路由是登录页面和主界面,在进入主界面之后只显示头部和侧导航栏,中间的内容是不显示的,这时如果使用重定向,在进入主界面之后就自动显示二级路由的一部分内容。 // 或者 输入网址后但进入的网站的网址是另一个的情况,网址有风险的情况

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

别名:/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样. 使用的时候很少

在路由配置项中添加:alias:"/abc"

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

“别名”的功能让你可以*地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

别名的使用场景:不用按照子父级关系定义路径了,之前子路由的路径得在父路由路径的基础上添加,使用别名后,可以直接写子路由的路径,不用再受父级路径的影响了,但是新版本修改后 不用别名就已经支持了。

路由组件传参(针对动态路由而言的)

可以将params直接映射到组件的属性中。

步骤:将路由组件中的路由配置项的属性props设置为true,这样动态路径就会作为参数传入到组件中,然后在组件中接收一下就可以直接用 。

使用动态路由this.$route.params.id之前的做法,能拿但是很麻烦:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
    //2 在组件中要拿到id
}


const router = new VueRouter({
  routes: [{ path: '/user/:id', component: User }]
    //1 动态路由/:id
})

用了组件传参后:

const User = {
  props: ['id',"num"],
    //2 在相应的组件中的js中(不是放在data中,与data同级)接收一下传过来的props后可直接使用
  template: '<div>User {{ id }} {{ num }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id/:num', component: User, props: true },
      //1 将路由组件中路由配置项的属性props设置为true,这样动态路径就会作为属性参数传入到组件中,然后在组件中接收一下就可以直接用 
  ]
})

props的来源:现在props可能是父-子传值中父组件传过来的,也有可能是路由中带的。

路由模式

默认的路由模式是hash模式,在创建项目的时候有问是否选择history模式,我们当时选择的是N。

hash模式的特点:URL地址栏是拼接在#后面的,#是一定会在的,即使是删掉也会在。hash模式丑,实际中都不使用。

改路由模式的方法:在路由文件创建路由对象时添加一个mode属性

const router=new VueRouter({
    routes,
    mode:"history" //将路由模式改为history模式
})

hash模式和history模式:

(面试题)history模式使用时存在的最大问题:是会导致刷新404的问题。

原因:hash模式是基于hash值来做的,#后面的hash值是不会发往后台的,那是前端自己定义的路由自己用的,发送请求查找服务器时是会忽略掉#后面的内容的,是按照#前面的内容查找的,是能成功查找到的;而history模式是按照完整的路径地址查找的,刷新时会按照完整的路径去后台服务器中查找,但是由于这个地址时前端定义的,在服务器是没有这个路径的,是无法在后台找到的,就会发生找不到页面404。(如hash模式的http://localhost:8080/#/home在向服务器发送请求时发送的地址只是#前面的http://localhost:8080/,是能够成功找到的;而history模式发送请求时使用的是完整的地址http://localhost:8080/home,该地址在服务器中是找不到的,出现404。要解决这个问题就需要后端配置nginx反向代理做重定向,当请求的路径不存在时重定向到首页。)

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

要解决这种问题就需要后端做一个nginx做一个反向代理,做重定向,如果请求的路径不存在,重定向到首页。----这个问题是由后端配置的,

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

如果面试官继续往下问是怎么做重定向的?那就回答是由后端做nginx代理做的,不是前端做的。

导航守卫/路由守卫

限制哪些能跳转,哪些不能跳转。

使用场景:如淘宝中未登录就支付是不可以的,有路由守卫拦下来。/ 或者权限管控。

全局前置守卫

全局:访问项目中的任何一个路由都会拦下来;前置:在还没跳转之前就拦下来。

使用场景:一般用来做整个项目的管控,如未登录时要求要先去登录。

beforeEach不用主动去调用,当有组件之间的跳转时就会自动的触发。

//在router文件夹下的index.js文件中创建的路由实例
const router = new VueRouter({ ... })
                              
//beforeEach方法是我们创建的路由实例router的方法
router.beforeEach((to, from, next) => {
  //根据判断条件能否进入到下一个页面
})
  • to: Route: 即将要进入的目标 路由对象 (路由对象就是this.$route拿到当前组件的路由信息)
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。—next方法需要主动调用。下面第一种和第三种是用的最多的。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。主动调用next()表示放行,直接往下进入下一个组件。----------------使用较多的
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。从哪来回哪去。
    • next('/')** 或者 **next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-linkto proprouter.push 中的选项。强制去另一个路径。(如淘宝种未登录去支付被强制转换到登录页面)------------------使用较多的
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

注意避免死循环,下面的写法是死循环

let flag = false;//存储的是登录的状态
//全局前置守卫 
router.beforeEach((to, from, next) => {
  if(flag){
    next();//直接放行
  }else{
    next("/login") 跳转到登录页面。在未登录状态时会发生死循环
  }
})

先看是否登录,未登录要去登录页面,而想要去登录页面又要检查是否登录,一看未登录就要去登录页面,而主只要想去其他页面就要进行检查,这样就陷入了死循环。

解决方法:在状态是未登录,需要转向登陆页面时做一个判断,判断当前是否是要去登录页面,如果是的话放行,否则还是得要去登陆页面。加个验证就ok了。

let flag = false;//存储的是登录的状态
//全局前置守卫 
router.beforeEach((to, from, next) => {
  if (flag) {
    next();//直接放行
  } else {
    //在是未登录状态需要转向登录页面时加一个验证:如果是要去登录页面的,就让他过去,否则如果是要去其他页面的话不让过,还是需要去登陆页面
    if (to.path == '/login') {
      next()
    } else {
      next('/login');//跳转到登录页面
    }
  }
})

全局解析守卫(用的很少)

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

全局前置守卫和全局解析守卫只是守卫的时机不同。全局前置守卫是在进大门的时候检查,而全局解析守卫是在进入大门之后再进入每一个小门的时候检查。

全局后置钩子

钩子:在特定的情况下会自动运行的函数。

跳转到某个页面之后需要做事情,一般可以用组件的生命周期来写。全局后置钩子与组件自己的生命周期类似,一般就不用了。

//和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
router.afterEach((to, from) => {
  // ...
})

路由独享的守卫

不需要给全局加守卫,只给其中某些重要的页面加守卫。beforeEnter:(to,from,next)=>{}

//在路由配置项中写的
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

组件内的守卫

在对应的组件.vue中写。

    export default {
        mounted(){
               console.log(this.$route)
        },
        beforeRouteEnter(to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
      },
      beforeRouteUpdate(to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
          //使用场景:当有动态路径发生改变,想要在动态路径改变时做事情,组件生命周期是不会发生不会的,但是使用组件内的守卫是会发生变化的。
      },
      beforeRouteLeave(to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }
    //进入该路由时执行,该路由中参数改变时执行,离开该路由时执行。

完整的导航解析流程

全局守卫先于局部守卫的执行。

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

路由元信息

定义路由的时候可以配置 meta 字段。通过this.$route拿到meta信息。

const routes = [
  {
    path: '/home',
    name: "Home",
    meta:{a:"123"},//meta路由元信息
    component: () => import('@/views/MyHome'),
    children: [
      {
        path: 'about',
        name: 'About',
        alias: '/abc',
        meta:{auth:"boss"},//meta路由元信息,权限,判断当前组件的meta是否是boss
        component: () => import('@/views/MyAbout'),
      }
    ]
  },
]

使用场景:meta可以设置一些跟当前路由相关的自定义信息,比如页面标题,面包屑导航,页面权限等。

滚动行为

由于页面内容较多出现了滚动条,上面的内容不是我们想要看的,下面才是,我们想要跳转页面过来后直接定位到想要看的部分,不想手动向下滑动。

有滚动条的前提下才生效,没有滚动条不生效

在路由实例中加:

const router = new VueRouter({
  routes,
  //更改路由模式
  mode: "history",
  //滚动行为
  scrollBehavior(to, from) {
    //to from是路由对象 我们不想所有的页面都滚动到相同的位置,to用于判断去到的是a页面滚到这里,如果去到的是b页面滚动到那里
    console.log(to, from)
      
      
    // return 期望滚动到哪个的位置   x是离左边距离  y是离顶部距离
    //有滚动条的前提下才生效,没有滚动条不生效
    return { x: 0, y: 1000 }
  }
})
上一篇:恋爱脑讲编程:Rust 的所有权概念


下一篇:Day 1 数据结构绪论