Vue Router

513 阅读13分钟

1、概述

vue-router和vue.js是深度集成的,适合用于单页面应用.传统的路由是用一些超链接来实现页面切换和跳转.而vue-router在单页面应用中,则是组件之间的切换。其本质就是:建立并管理url和对应组件之间的映射关系。

2、使用

2.1、安装vueRouter4 :

npm install vue-router@4

2.2、配置路由实例 router.js文件

//1、路由映射表,createWebHashHistory是创建历史模式的路由
import {createRouter,createWebHashHistory,createWebHistory} from 'vue-router'

//2、引入页面组件
import Home from '../components/Home.vue'
import Mine from '../components/Mine.vue'

//3、创建路由对象
const routes=[
  {path:'/home',component:Home},
  {path:'/mine',component:Mine}
]

//4、创建路由
const router=createRouter({
  history:createWebHashHistory(),//设置路由方位方式
  routes
})

//5、导出路由
export default router

2.3、在main.js中引入router.js并挂载到Vue实例

import { createApp } from 'vue'
import App from './App.vue'
//引入路由对象
import router from './router'

const app = createApp(App)
//使用路由
app.use(router)
app.mount('#app')

2.4、vue组件中使用路由

<template>
<div>
  <!-- 导航 -->
    <div id="nav">
      <router-link to="/home">首页</router-link>
      <router-link to="/mine">我的</router-link>
    </div>
  <!-- 路由出口 -->
    <router-view></router-view>
</div>
</template>
<script >
  export  default {
    setup (props,context){
      return {
      }
    }
  }
</script>
<style>
</style>
  • router-link:映射路由,就是创建a标签来定义路由导航的链接(用户通过点击实现跳转),通过to属性指定目标地址,默认渲染成带有正确链接的<a>标签。
  • router-view:就是在标签内渲染你路由匹配到的视图组件。

2.5、使用路由重定向

使用direct进行路由重定向。

//3、创建路由对象
const routes=[
    //路由重定向
    {path:'/',redirect:'/home'}, 
    {path:'/home',component:Home},
    {path:'/mine',component:Mine}
]

2.6、router-link常用属性active-class

active-class:添加一个激活的样式。

  <div id="nav">
    <router-link to="/home" active-class="current">首页</router-link>
    <router-link to="/mine"  active-class="current">我的</router-link>
  </div>

此外,router-link有一个默认的选中的样式类

.router-link-active {
  font-size:18px;
  color: golden;
  font-weight: bolder
}

2.7、router-link常用属性replace

replace重新替换一个栈,而不会有缓存。

  <div id="nav">
    <router-link to="/home" replace>首页</router-link>
    <router-link to="/mine" replace>我的</router-link>
  </div>

3、 vue-router的两种模式

一般单页面应用是(SPA)不会请求页面而是只更新视图. vue-router提供了两种方式来实现前端路由:Hash模式和History模式,可以用mode参数来决定使用哪一种方式。

3.1、Hash模式

vue-router默认使用Hash模式,使用url的hash来模拟一个完整的url。此时url变化时,浏览器是不会重新加载的.Hash(即#)是url的锚点,代表的是网页中的一个位置,仅仅改变#后面部分,浏览器只会滚动对应的位置,而不会重新加载页面。#仅仅只是对浏览器进行指导,而对服务端是完全没有作用的!它不会被包括在http请求中,故也不会重新加载页面。同时hash发生变化时,url都会被浏览器记录下来,这样你就可以使用浏览器的后退了。

总而言之:Hash模式就是通过改变#后面的值,实现浏览器渲染指定的组件.

3.2、History模式

如果你不喜欢hash这种#样式,可以使用history模式,这种模式利用了HTML5 History新增的pushState()和replaceState()方法。 除了之前的back,forward,go方法,这两个新方法可以应用在浏览器历史记录的增加替换功能上,使用History模式,通过历史记录修改url,但它不会立即向后端发送请求。注意点: 虽然History模式可以丢掉不美观的#,也可以正常的前进后退,但是刷新f5后,此时浏览器就会访问服务器,在没有后台支持的情况下,此时就会得到一个404!官方文档给出的描述是:"不过这种模式要玩好,还需要后台配置支持.因为我们的应用是单个客户端应用,如果后台没有正确的配置,当用户直接访问时,就会返回404,所以呢,你要在服务端增加一个覆盖所有情况的的候选资源;如果url匹配不到任何静态资源,则应该返回同一个index.html页面。"

总而言之:History模式就是通过pushState()方法来对浏览器的浏览记录进行修改,来达到不用请求后端来渲染的效果.不过建议,实际项目还是使用history模式.

例子:

const router = createRouter({
  history: createWebHashHistory(), //如果这里不写,路由默认为hash模式
  routes
})

4、动态路由匹配

当我们需要把某种模式匹配到所有的路由,全部都映射到同个组件.例如:我们有一个User组件,对于所有ID各不相同的用户,都要使用这个组件来渲染.这时我们就可以配置动态路由来实现. 动态路由匹配本质上就是通过url进行传参

4.1、路由对象属性介绍

为了下面理解的方便这里简单介绍下常用的路由对象属性,在组件内可以通过$route(不是$router!)进行访问。

$route.path 类型: string 字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。

$route.params 类型: Object 一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。

$route.query 类型: Object 一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。

$route.name 当前路由的名称,如果有的话。这里建议最好给每个路由对象命名,方便以后编程式导航.不过记住name必须唯一!

$route.hash 类型: string 当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。

$route.fullPath 类型: string 完成解析后的 URL,包含查询参数和 hash 的完整路径。

4.2 使用params进行配置:

routes:[{
	//动态路径参数,以冒号开头
	path:'/user/:id',
	component:User
}]

这样,就是使用params进行配置,像/user/foo和/user/bar都将映射到相同的路由。

  • 一个路径参数使用':'冒号进行标记。
  • 当匹配到一个路由时,参数就会被设置到$route.params,可以在每个组件内使用。例如/user/foo在$route.params.id就为foo。

这里以官方的表格示例进行展示

模式匹配路径$route.params
/user/:username/user/evan{ username: 'evan' }
/user/:username/post/:post_id/user/evan/post/123{ username: 'evan', post_id: 123 }

这里再举一个稍微变化一下的例子,来加深理解:

routes:[
	{path:'/user/:shot/foo/:id', component:shotCat}
]
<router-link to="/user/shot/foo">/user/shot/foo</router-link>  	<!--无法匹配到对应路由-->	
<router-link to="/user/shot/cat/foo">/user/shot/cat/foo</router-link>	<!--无法匹配到对应路由-->
<router-link to="/user/foo/foo/foo">/user/foo/foo/foo</router-link>	<!--成功匹配,$route.params.shot为foo;$route.params.cat为foo;-->	
<router-link to="/user/shot/foo/cat">/user/shot/foo/cat</router-link><!--成功匹配,$route.params.shot为shot;$route.params.cat为cat;-->	
<router-view></router-view>

tips:

  • 有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序,谁先定义的,谁的优先级就最高。

  • 由于路由参数对组件实例是复用的,例如:/user/foo 和 /user/bar在使用路由参数时,复用的都是User组件.此时组件的生命周期钩子不会再被调用。

4.3、通过query进行配置传参

在项目里我们可以通过上面提到的params进行传参,同时也可以用query进行传参。举个例子: <router-link to="/user?id=foo">foo</router-link> vue-route会自动将?后的id=foo封装进route.query里。此时,在组件里route.query里。此时,在组件里route.query.id值为'foo'。除了通过router-linkto属性. query也可以通过后面讲到的编程式导航进行传参。

5、编程式导航

什么是编程式导航,编程式导航就是在vue组件内部通过router访问路由实例,并通过router.push导航到不同的url,进行路由映射,所以 它的作用是和<router-link :to>是一样的! 当然,前提是你已经在routes里配置了对应的路由对象。

一般什么时候用到编程式导航? 如果,你想在路由跳转前做点其他事情,例如权限验证等.但是用<router-link>的话,就直接跳转了.此时就可以使用编程式导航!

5.1、编程式导航的写法

编程式导航一般都是用到router.push方法,该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

<script>
import { useRouter } from "vue-router";
export default {
  setup() {
    const router = useRouter();
    //字符串
    const strHome = ()=>{router.push("home")};
    //对象
    const objHome = ()=>{router.push({ path: "home" })};
    //命名路由
    const nameUser = ()=>{router.push({ name: "user", params: { userId: 2333 } })};
    //带查询参数,变成/register?plan=private
    const queryRegister = ()=>{router.push({ path: "register", query: { plan: "private" } })};
    return {
      strHome,
      objHome,
      nameUser,
      queryRegister
    };
  },
};
</script>

注意:pathparams是不能同时生效的! ,否则params会被忽略掉。所以使用对象写法进行params传参时,要么就是path加冒号:,要么就是像上例中的'命名路由',通过name和params进行传参。然而query却并不受影响,有没有path都可以进行传参。

5.2、router.replace方法

router.replace和router.push很像,写法一样.但实际效果不一样。push是向history里添加新记录,而replace是直接将当前浏览器history记录替换掉!

那最直接的后果是什么呢? 举个例子:

  • 用push方法,页面1跳转到页面2,你使用浏览器的后退可以回到页面1。
  • 用replace方法,页面1被替换成页面2,你使用浏览器的后退,此时你回不到页面1,只能回到页面1的前一页,页面0。

那什么时候会用到replace呢? 当你不想让用户回退到之前的页面时,常见于权限验证,验证后就不让用户回退到登录页重复验证。

6、嵌套路由与单组件多视图

  • 嵌套路由:就是父路由嵌套子路由,url上就是/user嵌套两个子路由后就是/user/foo和/uer/bar,用一张图表示就是:

嵌套路由

  • 单组件多视图:就是一个组件里有多个视图进行展示.即包含有多个<router-view/>

6.1、嵌套路由

讲之前,必须先清楚这样一件事,一个<router-view/>对应展示的就是一个组件 因此实现嵌套路由有两个要点:

  • 路由对象中定义子路由(嵌套子路由)
  • 组件内<router-view/>的使用.

6.2、路由对象中定义子路由

const routes = [
  {
    path: '/user', component: User, name: 'user',
    //嵌套路由就写在children配置中,写法和rutes一样.
    children: [
      {
        path: '', component: UserDefault, name: 'default',
        //children:[{}]   也可以继续添加children嵌套
      },
      //如果/user下没有匹配到其他子路由时,User的<router-view>是什么都不会显示的,如果你想让它显示点什么.可以将path:''.设为空.此时UserDefault就是默认显示的组件.
      { path: 'foo', component: UserFoo, name: 'foo' },
      //此时path等同于'/user/foo',子路由会继承父路由的路径.但是不能写成path:'/foo'.因为以 / 开头的嵌套路径会被当作根路径,也就是说此时foo成了根路径.而不是user.
      { path: 'bar', component: UserBar, name: 'bar' }
    ]
  }
]

6.3、组件内<router-view/>的使用.

<template>
  <div id="nav">
    <p>
      <router-link to="/user">/user</router-link>
      <router-link to="/user/foo">/user/foo</router-link>
      <router-link to="/user/bar">/user/bar</router-link>
    </p>
    <router-view></router-view>
    <!--这里展示的是User组件;同样User的<router-view/>也被嵌套在里面-->
  </div>
</template>
<template>
  <div class="user">
    <h2>User</h2>
    <router-view></router-view> //User的<router-view>里展示的就是子路由foo,bar的组件还有default默认组件
  </div>
</template>
<template>
这里是UserDefault页面
</template>
<template>
这里是UserFoo页面
</template>
<template>
这里是UserBar页面
</template>

7、路由懒加载

组件在路由访问的时候才加载,而不是一开始就加载,从而更加高效。

路由index文件:

//路由懒加载
const Home=()=>import('./../components/Home.vue')
const Mine=()=>import('./../components/Mine.vue')
const News=()=>import('./../components/News.vue')

案例:

// 1、路由映射表,createWebHashHistory是创建历史模式的路由
import {createRouter,createWebHashHistory,createWebHistory} from 'vue-router'

//2、引入页面组件
// import Home from './../components/Home.vue'
// import Mine from './../components/Mine.vue'
// import News from './../components/News.vue'

//路由懒加载
const Home=()=>import('./../components/Home.vue')
const Mine=()=>import('./../components/Mine.vue')
const News=()=>import('./../components/News.vue')

//3、创建路由对象
const routes=[
  {path:'/',redirect:'/home'},
  {path:'/home',component:Home},
  {path:'/mine',component:Mine},
  //动态路由,动态绑定id,要使用冒号:
  {path:'/news/:id',component:News}
]

//4、创建路由
const router=createRouter({
  history:createWebHashHistory(),//设置路由方位方式
  routes
})

//5、导出路由
export default router

8、导航守卫

  • 全局前置守卫就是在进入一个路由之前会调用。
  • 全局后置钩子就是在进入一个路由之后会调用。 我们在router.js文件中添加代码
const router = createRouter({
  history: createWebHistory(),
  routes,
})
// 增加
router.beforeEach((to, from) => {
  console.log('进入页面之前会调用')
  console.log('要进入的页面:' + to)
  console.log('上一个页面:' + from)
})
router.afterEach((to, from) => {
  console.log('进入页面之后会调用')
  console.log('要进入的页面:' + to)
  console.log('上一个页面:' + from)
})

在路由变化时,这两个方法就会被调用,比如我们从首页进入列表页时,控制台输出如下

image.png

beforeEach先执行,afterEach后执行,它们都接收两个参数。to是要进入的页面的route对象,from是上一个页面的路由对象。
区别在于,如果在beforeEach的函数中返回false,就可以阻止进到下一个页面,我们现在写一个功能,不可以进入用户详情页。

router.beforeEach((to, from) => {
  console.log('进入页面之前会调用')
  console.log(to)
  console.log(from)
  if (to.name === 'userDetail') {
    return false // 不准进入
  } else {
    return true
  }
})

在工作中,beforeEach用到得最多的也是这样的场景,判断用户有没有权限访问某个页面,没有的话就返回false,用户就不能访问了。
afterEach最常用的就是在进入一个页面以后,需要向后台发送埋点数据,方便运营人员做数据分析。

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

9、路由元信息

9.1、什么是路由元信息

一句话概括:路由配置的meta对象里的信息:

const routes = [
  {
    path: '/foo',
    component: Foo,
    children: [
      {
        path: 'bar',
        component: Bar,
        // a meta field
        meta: { requiresAuth: true }
      }
    ]
  }]

从上面可以看出就是给路由添加了一个自定义的meta对象,并在里面设置了一个requiresAuth状态为true。

9.2、它有什么用

从下面的另一个官方栗子里已经给出了答案。

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
//数组some方法,如果meta.requiresAuth为ture,则返回true.此时,说明进入该路由前需要判断用户是否已经登录 
    if (!auth.loggedIn()) {   //如果没登录,则跳转到登录页
      next({
        path: '/login',
        query: { redirect: to.fullPath }  //官方例子的这个小细节很好,通过query将要跳转的路由路径保存下来,待完成登录后,就可以直接获取该路径,直接跳转到登录前要去的路由
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

我们可以通过在meta里设置的状态,来判断是否需要进行登录验证.如果meta里的requiresAuth为true,则需要判断是否已经登录,没登录就跳转到登录页.如果已登录则继续跳转.

此时,可能会有同学说,前面说的path,params,query都可以存储信息,作为登录验证的状态标记.的确,它们也可以达到同样的效果.如果是少量单个的验证,使用它们问题不大. 但如果是多个路由都需要进行登录验证呢?path,params,query是把信息显性地存储在url上的.并且多个路径都把一个相同的状态信息加在url上.这样就使url不再单纯,并且也很不优雅美观. 所以要优雅要隐性地传递信息,就使用meta对象吧!

10、keep-alive

keep-alive是Vue的内置组件,作用是将组件缓存在内存当中,防止重复渲染DOM,属于消耗内存获取速度。常用的用法是将组件或者路由缓存。

10.1、使用

通常我们可以配置整个页面缓存或只让特定的某个组件保持缓存信息,配置了keepalive的路由或者组件,只会在页面初始化的时候执行created->mounted生命周期,第二次及以后再进入该页面将不会执行改生命周期,而是会去读取缓存信息。

10.1.1、router配置缓存

1)第一步:配置App.vue

<template>
  <!-- vue3.0配置 -->
  <router-view v-slot="{ Component }">
    <keep-alive>
      <component :is="Component"  v-if="$route.meta.keepAlive"/>
    </keep-alive>
    <component :is="Component"  v-if="!$route.meta.keepAlive"/>
  </router-view> 
</template>

这里component是vue中的特殊组件,:is是用来绑定指定组件,这里是与路由对应的页面绑定。

2)第二步:添加meta属性

在对应的路由上添加meta属性来设置页面是否要使用缓存,如下:

{
  path: "/keepAliveTest",
  name: "keepAliveTest",
  meta: {
       keepAlive: true //设置页面是否需要使用缓存
  },
  component: () => import("@/views/keepAliveTest/index.vue")
}

到此即可实现页面的简单缓存,但是有些场景需要做复杂处理,比如说页面部分信息不需要读缓存,每次进入都需要进行处理,这个时候我们就可以使用activated生命周期来解决页面部分刷新问题。

3)实现页面部分刷新

先了解vue的生命周期,被keepAlive包裹的组件和页面,页面进入时执行的生命周期为:created->mounted->activated; 其中created->mounted是页面第一次进入才会执行,activated生命周期在页面每次进入都会执行,属于keepAlive的一个生命周期,所以我们把页面每次进来要进行的操作放入该生命周期即可。

activated() {
	// 页面每次进入将手机动态验证码置为空
   this.$refs.mobPwdCode.inputValue = '';
},

4)动态设置路由keepAlive属性

有些时候我们用完了keepalive缓存之后,想让页面不再保持缓存,或者设置下一个页面keepalive,这个时候我们可以改变meta的keepAlive值来去除页面缓存,使用beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave,使用方式如下:

// to为即将跳转的路由,from为上一个页面路由
beforeRouteLeave(to, from, next) {
    // 设置下一个路由的 meta
    to.meta.keepAlive = false;
    next();
}

参考:juejin.cn/post/684490…