Vue3(三):Vue-Router详解
目录
前言
Vue3的官方路由是Vue-Router4,它是Vue.js官方提供的路由管理器,它提供了一系列的功能来构建单页面应用。通过配置路由,可以定义URL与组件之间的映射关系,用来实现导航、参数传递、嵌套路由、路由守卫等功能
1、vue-router基本使用
安装路由
npm i vue-router
创建路由实例
我们在项目的src目录建一个router文件夹,用于存放于路由相关的文件,在router/index.js文件中通过createRouter来定义路由模式和路由规则
//router/index.js文件
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import Login from "@/views/Login.vue";
//定义路由规则
const routes = [
{
path: "/home",//url路径
name: "Home",//路由名称,我们也可以根据路由的名字来进行跳转
component: Home//对应渲染组件
},
{
path: "/login",
name: "Login",
component: Login
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
注:我们在进行路由配置的时候,最好给路由取个name,当项目比较复杂,路径很长的时候,可以使用name进行路由跳转
路由懒加载
路由懒加载就是利用es6的import函数和webpack构建工具的代码分割功能,将组件代码拆分到单独的js文件中,只在用户访问对应路由时才加载该文件,实现了按需加载,并不是初始时一次性加载所有组件,可以减少首屏加载体积,提升加载速度
路由懒加载最常用的方式就是在路由配置的文件中直接将静态的import替换为动态的import就可以了
//router/index.js文件
import { createRouter, createWebHistory } from "vue-router";
//import Home from "../views/Home.vue";改为动态引入
//import Login from "@/views/Login.vue";
//定义路由规则
const routes = [
{
path: "/home",//url路径
name: "Home",//路由名称,用于编程式导航
component: () => import("@/views/Home.vue")//路由懒加载
},
{
path: "/login",
name: "Login",
component: () => import("@/views/Login.vue")//路由懒加载
},
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
在main.js中挂载
一旦创建了路由器实例,就需要将其注册为插件,我们在main.js中,通过app.use()来注册路由
//main.js文件
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
const app = createApp(App)
app.use(router).mount('#app')
在App.vue中使用路由
在App.vue组件中通过<router-view>来为路由提供渲染出口
<template>
<div>
<!-- 路由渲染出口 -->
<RouterView></RouterView>
</div>
</template>
2、嵌套路由
嵌套路由就是在一个父路由组件内部,渲染一个或多个子路由组件,实现页面内容的层级嵌套
定义嵌套路由
在路由配置中,通过children属性来定义嵌套路由,children属性是一个数组,里面的每一个元素都是一个路由配置对象
import { createRouter, createWebHistory } from "vue-router";
//定义路由规则
const routes = [
{
path: "/home",//url路径
name: "Home",//路由名称,用于编程式导航
component: () => import("@/views/Home.vue"),//对应渲染组件
meta: {
keepAlive: true//需要缓存
},
//嵌套路由
children:[
{
path:"",//默认子路由
name:"MyChild",
component:()=>import("@/views/MyChild.vue")
},
{
path:"circle",
name:"Circle",
component:()=>import("@/views/Circle.vue")
}
]
}]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
注:当嵌套路由有默认子路由时,访问父路由路径会渲染默认子路由组件
在父组件中设置路由出口,需要使用<router-view>来作为子组件的渲染出口
//Home.vue组件
<template>
<div>
<p>我是主页</p>
//路由渲染出口
<router-view></router-view>
</div>
</template>
注:子路由的路径不能加"/",子路由的路径是基于父路由路径进行拼接的
嵌套路由的导航
在嵌套路由中,可以使用绝对路径或相对路径进行导航跳转
<template>
<div>
<p>我是主页</p>
<!-- 绝对路径 以/开头-->
<router-link to="/home/circle"></router-link>
<!-- 相对路径 -->
<router-link to="circle"></router-link>
<router-view></router-view>
</div>
</template>
3、路由跳转
在vue3中,进行路由跳转有两种方式声明式导航和编程式导航
1、声明式导航
声明式导航是在模板内使用的,通过<router-link>组件实现的,会自动渲染为<a>标签,不需要手动处理href和页面刷新
通过to属性指定目标路由路径或命名路由
<template>
<div>
//跳转到指定路径
<router-link to="/home">首页</router-link>
//跳转到命名路由,需要在路由规则中配置name
<router-link to="{name:"Home"}">首页</router-link>
</div>
</template>
2、编程式导航
编程式导航是在js脚本中使用的,通过组合式函数useRouter获取路由实例,调用其方法实现路由跳转,适合在逻辑处理后触发,如登录成功后
useRouter是组合式函数,它主要用于获取路由实例,它提供了很多方法,如push,replace,可以实现路由的跳转,操作历史记录等等

先引入并获取router实例
<script setup>
import { useRouter } from "vue-router";
const router = useRouter(); //获取路由实例
</script>
如果跳转到目标路由,想添加历史记录的话,可以使用router.push()进行跳转
<script setup>
//跳转路径
router.push("/login");
</script>
如果跳转到目标路由,想替换当前路由不添加历史记录,也就是不能回退到当前页,可以使用router.replace()
<script>
router.replace("/login")//登录后不想回退到之前页面时使用
</script>
我们也可以使用router.go(n),控制历史记录跳转,n为数据
<script>
router.go(-1)//后退到上一页,等同于router.back()
router.go(1)//前进到下一页,等同于router.forward()
</script>
4、路由传参
1、动态路由传参
动态路由参数是路由模式的一部分,通常用于传递必要的数据,如用户id
动态路由传参就是在路由配置中,使用冒号来定义动态参数
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
import User from "../views/User.vue"
//定义路由规则
const routes = [
{
path: "/user/:id",//动态路由部分,也可以简单理解为多个路径匹配同一个界面
name: "User",
component: User
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
从App.vue组件跳转到User.vue页面,先在App.vue页面定义要传递的数据
//App.vue组件
<script setup>
import { useRouter } from "vue-router";
const router = useRouter(); //获取路由实例
function jump() {
router.push("/user/123");//123为传递到User.vue组件的参数
}
</script>
在User.vue组件中通过组合式函数useRoute来获取参数
useRoute也是组合式函数可以用于获取当前激活路由的完整信息对象,是只读的不能进行修改,它的常用属性有path、params、query、meta等等

//User.vue组件
<template>
<div>
<p>{{ $route.params.id }}</p>//$route与useRoute是一样的可以获取当前激活路由的信息对象
</div>
</template>
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.params.id); //获取动态参数id
</script>
2、查询参数
查询参数也就是query传参,适用于传不敏感的数据,如用户id、页码等等,参数会显示在url中,
声明式导航中传参
查询参数就是在要传递数据的组件中,以?开头,多个参数用&分隔
<template>
<div>
<router-link to="/login/?name=李四&age=19>首页</router-link>
<!-- 路由渲染出口 -->
<RouterView></RouterView>
</div>
</template>
编程式导航中
<script setup>
import { useRouter } from "vue-router";
const router = useRouter(); //获取路由实例
function jump() {
//跳转到登录页
router.push({ path: "/login", query: { name: "李四", age: 19 } });//传递对象
router.push(`/home?id=1&name=王五`)
}
</script>
在接受数据的组件中,使用useRoute来获取传来的参数
//Login.vue组件
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.query.name);
</script>
3、params传参
编程式导航中不仅可以使用query传参,也可以使用params,如果使用params传参,在路由配置中如果没有定义相应的参数,则参数不会显示在url上,并且刷新页面后参数会丢失。如果想要参数在url上体现,并且刷新页面不会丢失,可以使用动态路由参数或查询参数
路由路径须定义参数占位符,参数不会显示在url中,适用于传递敏感的数据
配置动态路由
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
import User from "../views/User.vue"
//定义路由规则
const routes = [
{
path: "/user/:id",//动态路由部分,也可以简单理解为多个路径匹配同一个界面
name: "User",
component: User
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
跳转时传参
跳转传参有两种方式,第一种是字符串拼接,另一种是传递一个对象
字符串拼接需要与动态路由占位符对应,只能传递动态路由中定义的参数,额外的参数会丢失
//App.vue组件
<script setup>
import { useRouter } from "vue-router";
const router = useRouter(); //获取路由实例
function jump() {
router.push(`/user/123456`);//字符串拼接
}
</script>
传递对象不能使用path,只能用name匹配路由
<script setup>
import { useRouter } from "vue-router";
const router = useRouter(); //获取路由实例
function jump() {
router.push({
name: "User",
params: { name: "李四", age: 19 },//传递的参数
});
}
</script>
接收参数
//User.vue组件
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.params.age); //"19"参数同样是字符串类型
</script>
4、props传参
我们也可以将路由参数作为组件的props进行传递,这样组件就不需要直接依赖$route对象了,可以让组件更易于复用和测试
使用props传参,我们可以在路由配置中开启props,props可以是布尔模式、对象模式、函数模式
布尔模式
当我们将props设置为true时,route.params将被设置为组件的props
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
import User from "../views/User.vue"
//定义路由规则
const routes = [
{
path: "/user/:id",//动态路由部分,也可以简单理解为多个路径匹配同一个界面
name: "User",
component: User,
props:true//开启了props
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
对象模式
对象模式就是可以传递一个静态对象作为props
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
import User from "../views/User.vue"
//定义路由规则
const routes = [
{
path: "/user/:id",//动态路由部分,也可以简单理解为多个路径匹配同一个界面
name: "User",
component: User,
props:{id:"1234"}//传递一个对象
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
函数模式
可以基于路由返回一个对象,允许将参数和查询参数等转换为props
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
import User from "../views/User.vue"
//定义路由规则
const routes = [
{
path: "/user/:id",//动态路由部分,也可以简单理解为多个路径匹配同一个界面
name: "User",
component: User
props:(route)=>({id:route.params.id,query:route.query})
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
在组件中使用props
<template>
<div>
<p>{{ id }}</p>
</div>
</template>
<script setup>
defineProps({
id: {
type: String,
required: true,
},
});
</script>
5、路由元信息
路由元信息可以让我们在路由配置中添加meta字段,用于存储一些与路由相关的信息,这些信息可以在导航守卫、路由对象以及组件内部访问
在路由配置文件中,定义元信息
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
import User from "../views/User.vue"
//定义路由规则
const routes = [
{
path: "/user/:id",//动态路由部分,也可以简单理解为多个路径匹配同一个界面
name: "User",
component: User,
meta:{requireAuth:true,title:"管理员页面"}//元信息,携带一些额外数据
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
在组件中访问元信息
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.meta.requireAuth);
</script>
注:在做项目时,通常在权限控制、面包屑导航中使用路由元信息
5、路由缓存
在vue3中,我们可以使用KeepAlive组件来缓存包裹的路由组件实例,避免组件切换时重复的创建销毁,可以提升性能和用户体验
KeepAlive实现路由缓存,其实就是被KeepAlive包裹的组件,离开时会触发onDeactivated生命周期钩子,再次进入的时候会触发onActivated生命周期钩子,组件的实例和状态会完全保留
基本使用
我们可以在路由渲染出口中使用KeepAlive来缓存所有路由组件实例
<template>
<div>
<!-- 路由渲染出口 -->
<RouterView v-slot="{ Component }">
<keep-alive>
<component :is="Component"/>
</keep-alive>
</RouterView>
</div>
</template>
我们也可以使用<KeepAlive>组件自带的include或exclude还有max属性来控制缓存路由组件的数量
include:字符串或正则表达式,只有名称匹配的组件才会被缓存
exclude:字符串或正则表达式,任何名称匹配的组件都不会被缓存,,优先级要高于include
max:它是数字,最多可以缓存多少组件实例,来限制缓存实例的数量,避免内存过度使用
<template>
<div>
<!-- 路由渲染出口 -->
<RouterView v-slot="{ Component }">
//exclude就是排除name为About和User的组件
<keep-alive
:include="['Home', 'Login']"
:exclude="['About','User']"
:max="10"
>//双引号里面不能使用双引号,'["Home"]'会报错,我们可以使用单引号
<component :is="Component"></component>
</keep-alive>
</RouterView>
</div>
</template>
注:include和exclude是基于路由组件的name进行匹配的,我们也需要给路由组件设置name,不能只靠路由配置里的name,否则缓存不会生效
//路由组件Home.vue
<script setup>
defineOptions({
name: "Home",
});
</script>
我们也可以通过路由元信息来控制缓存,也就是通过在路由配置中添加meta属性来动态控制哪些路由需要被缓存
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
//定义路由规则
const routes = [
{
path: "/home",//url路径
name: "Home",//路由名称,用于编程式导航
component: () => import("@/views/Home.vue"),//对应渲染组件
meta:{
keepAlive:true//需要缓存
}
},
{
path: "/login",
name: "Login",
component: ()=>import("@/views/Login.vue"),
meta:{
keepAlive:false//不需要缓存
}
},
{
path: "/user/:id",
name: "User",
component: () => import("@/views/User.vue"),
meta:{
keepAlive:true//需要缓存
}
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
在路由的渲染出口,可以使用v-if进行条件渲染
<template>
<div>
<!-- 路由渲染出口 -->
<RouterView v-slot="{ Component,route }">
<keep-alive>
//需要缓存的路由组件
<component :is="Component" v-if="$route.meta.keepAlive"></component>
</keep-alive>
//不需要缓存的路由组件
<component :is="Component" v-if="!$route.meta.keepAlive">
</RouterView>
</div>
</template>
注:路由缓存的常见使用场景,在列表页进入详情页后,返回列表页时希望保持列表的位置和状态。在填写表单时,切换到其它页面再返回,表单数据不会丢失
6、导航守卫
Vue3导航守卫是控制路由跳转权限、监听路由变化的核心工具,可以简单理解为路由跳转前后执行的钩子函数,有全局守卫、路由独享守卫和组件内守卫三类
所有类型的守卫回调函数的参数都有3个固定的参数
to:即将进入的目标路由对象,包含path,name等属性
from:即将离开的当前路由对象
next:路由跳转的函数,在vue3中这个参数可选,我们可以直接返回路由对象或false代替
1、全局守卫
全局导航守卫有三个,分别是beforeEach、beforeResolve和afterEach,它们可以作用于整个应用的路由,需要在router实例上定义
我们可以在router文件夹下新建一个guard.js文件用于存放与导航守卫相关的代码

这个文件需要在项目的入口文件main.js中引入
//main.js
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from '@/router/index'
//导航守卫
import "@/router/guard.js"
const app = createApp(App)
app.use(router)
app.mount('#app')
beforeEach:它是最常用的导航守卫,全局前置守卫,在路由跳转前触发,可用于登录验证
//guard.js文件
import router from "./index.js"
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem("token")
//未登录且目标页面也不是登录页,就拦截
if (!isLogin && to.name !== "Login") {
next({name:'Login'})
}else{
next()//必须调用,否则导航无法挂起
}
})
afterEach:全局后置守卫,路由跳转后触发,没有next参数,常用于修改页面标题
router.afterEach((to,from)=>{
console.log("全局后置钩子")
})
beforeResolve:全局解析守卫,所有组件内守卫和异步路由组件解析后触发,比beforeEach晚,比afterEach早触发
// 全局解析守卫
// 在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用
router.beforeResolve((to, from, next) => {
console.log('全局解析守卫')
// 确保所有动态导入的组件已加载
if (to.meta.requiresPermission) {
// 检查用户权限
const hasPermission = checkUserPermissions(to.meta.requiredPermissions)
if (!hasPermission) {
next(false) // 中断导航
return
}
}
next()
})
2、路由独享守卫
路由独享守卫只有beforeEnter一个,只作用于指定路由,进入该路由时触发,在路由规则中定义
//router/index.js
import { createRouter, createWebHistory } from "vue-router";
//定义路由规则
const routes = [
{
path: "/user",
name: "User",
component: () => import("@/views/User.vue"),
// 进入 /user 路由前触发
beforeEnter: (to, from, next) => {
const hasPermission = checkUserPermission() // 自定义权限判断
hasPermission ? next() : next('/403') // 无权限跳403页
},
meta: {
keepAlive: true//需要缓存
}
}
]
//创建路由实例
const router = createRouter({
routes,//注册路由规则
history: createWebHistory()//路由模式,history模式
})
export default router
3、组件内守卫
组件内守卫就是在路由组件内部定义,控制组件与路由的绑定逻辑,常用的有三种onBeforeRouteEnter、onBeforeRouteUpdate、onBeforeRouteLeave
onBeforeRouteEnter:进入组件对应的路由前触发,此时组件实例未创建,this为undefined,需通过 next()访问组件
onBeforeRouteUpdate:同一组件下路由参数变化时触发,如 /user/1 跳 /user/2 ,可获取新参数
onBeforeRouteLeave:离开组件对应的路由前触发,常用于阻止未保存的表单跳转。
//List.vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from, next) => {
if (confirm('表单未保存,确定离开吗?')) next()
else next(false) // 取消跳转
})
</script>
7、相关面试题
1、<KeepAlive>原理
<KeepAlive>是vue3提供的内置组件,当做组件切换或缓存路由组件的时候,可以使用<KeepAlive>将组件包裹起来,它缓存的是组件实例而非组件本身,所以组件内的数据和状态会被保留。
当组件切出时,会触发 onDeactivated钩子,KeepAlive会将组件实例存入内部缓存对象并剥离其 DOM 节点暂存但不会被销毁。当再次切入组件时,会触发onActivated 钩子,先检查缓存,如果有的话,直接复用缓存的实例和DOM,跳过 created、mounted 等生命周期。它还通过include、exclude筛选需缓存的组件,max属性限制缓存数量,使用了LRU算法,最近最少使用,最近被使用或访问的数据会放在最前面,缓存数据被访问时,将数据移到头部,缓存数量达到最大值时,最近最少访问的数据会被剔除
2、路由的两种模式
Vue3 路由的两种核心模式是 hash 模式和history 模式,核心区别在于 URL 表现形式与底层原理的实现有所不同。
hash 模式它的URL包含 # 符号,# 后内容为路由路径,它是基于浏览器 window.onhashchange 事件,监听 # 后 URL 片段的变化,不会触发页面整体刷新。使用hash模式,我们无需进行后端配置,它的兼容性很强,支持所有现代浏览器及旧版 IE,部署简单。
history 模式它的URL 与正常网页一致,无 # 符号,更符合用户认知。它是基于HTML5的History API 如pushState 、 replaceState等方法,通过操作浏览器历史栈改变 URL,同样不刷新页面。但使用history模式,需要后端配合配置,如 Nginx或Apache,转发所有路由请求到 index.html,否则刷新非首页会报 404 错误。
3、导航守卫解析流程
面试时,我们先概括一下导航守卫,然后说一下执行流程就可以了。要想更详细可以再解释一下每个导航守卫
导航守卫是Vue Router提供的路由跳转过程中的钩子函数,用于控制和管理路由导航的完整生命周期,让我们能够在路由跳转的各个阶段插入业务逻辑,实现精细化的路由控制和用户体验优化。
导航守卫有全局守卫、路由独享守卫和组件内守卫三类。全局守卫有三个钩子,最常用的钩子是beforeEach,在路由跳转前触发,可用于权限验证,登录检查等等,当所有组件守卫和异步路由组件被解析后会调用beforeResolve,导航跳转完成后,会调afterEach。路由独享守卫只有一个就是 beforeEnter,它需要在路由规则中定义,只作用于指定路由,进入该路由时会触发。组件内守卫就是在组件内进行定义,最常用的有三个,onBeforeRouteEnter是在进入组件对应的路由前触发,此时组件实例未创建,this为undefined,需通过 next()访问组件。onBeforeRouteUpdate是在同一组件下路由参数变化时触发,可获取新参数。onBeforeRouteLeave是在离开组件对应的路由前触发,常用于阻止未保存的表单跳转。
导航守卫的执行流程可以分为三个阶段,第一个阶段就是导航触发与离开,会先触发失活组件的 beforeRouteLeave 守卫,接着触发全局前置守卫 beforeEach。第二个阶段就是路由匹配与组件解析,如果路由对应组件需重用,则执行组件更新守卫beforeRouteUpdate,随后调用该路由配置中的 路由独享守卫beforeEnter,进入特定路由,并解析路由包含的异步组件,组件解析完成后,执行激活组件的beforeRouteEnter。第三个阶段就是导航确认与完成,这个阶段会再触发全局解析守卫 beforeResolve,此时导航正式被确认,进而执行全局后置钩子 afterEach,待DOM更新完成后,最终触发 beforeRouteEnter中 next 的回调函数,整个导航解析流程就结束了。
面试时还可能会问:
beforeEach和beforeResolve有什么区别
beforeEach在导航开始时调用,适合做权限验证,beforeResolve在所有组件守卫解析后调用,适合做最终的状态确认
在beforeRouteEnter中如何获取数据
由于不能直接访问this,我们可以通过next的回调函数,或者更好的做法是在created生命周期中获取数据
4、params query传参区别
query是明文传递数据,一般用于非敏感数据传参,params是密文传参,数据不会在url上显示,适用于敏感数据传参。query页面刷新后仍能拿到跳转后的数据,params页面刷新后不能拿到跳转后的数据。query要使用路由对象的path属性进行跳转,params使用路由对象的name属性进行跳转
更多推荐


所有评论(0)