Vue Router
安装使用
1.初始化本地项目
npm create vite@latest用Vite创建的Vue3项目内容是空的,没有router、pinia等其他插件,需要自己集成。
npm install vue-router@4Vue3 安装的路由版本为router4
Vue2 安装的路由版本为router3
在src目录下新建router文件,然后在文件夹下新建index.ts路由文件。
import { createRouter, createWebHistory } from 'vue-router'
import Home from ('../Home.vue')
import Login from ('../Login.vue')
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/login',
name: 'login',
component: Login
}
]
})
export default router在main.ts中挂载到Vue实例
import { createApp } from 'vue';
import './style.css';
import App from './App.vue';
import router from './router/index';
createApp(App).use(router).mount('#app');2.路由跳转
使用 router-link 组件进行导航,通过to来指定跳转的链接,可以在不刷新页面的情况下更改URL,从而跳转到其他页面。
而router-view将显示与 URL 对应的组件
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router';
</script>
<div id="app">
<router-link to="/">Go to Home</router-link>
<router-link to="/login">Go to Login</router-link>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>路由模式
有两种路由模式Hash和History模式,分别通过createWebHashHistory和createWebHistory来设置,这也是常见的 Vue 面试题
1.Hash 模式
URL 示例:http://example.com/#/home
在URL中使用#符号来表示路由地址,即哈希片段。#符号后面的内容被称为哈希路径(hash path),由Vue Router用来匹配路由。浏览器不会将哈希片段发送到服务器,因此哈希模式不会影响服务器请求。
原理是通过onhashchange()事件监听hash值变化,在页面hash值发生变化后,window就可以监听到事件改变,并按照规则加载相应的代码。hash值变化对应的 URL 都会被记录下来,这样就能实现浏览器历史页面前进后退。
Hash易于部署,无需额外配置服务器;对于不支持HTML5 History API的浏览器,有较好的兼容性。但 URL 中包含#符号,有时可能被认为不够美观。
2.History 模式
URL 示例:http://example.com/home
在 URL 中不使用#符号,而是直接使用普通的 URL 路径。这需要服务器配置以支持该模式,并确保在用户访问页面时,服务器返回正确的资源而不是 404 错误。
history原理是使用HTML5 history提供的pushState、replaceState两个 API,用于浏览器记录历史浏览栈,并且在修改 URL 时不会触发页面刷新和后台数据请求。
URL 更加美观,没有#符号;适合用于实际生产环境中,通常需要与服务器配合,支持HTML5 History API。但是在生产环境中需要特殊配置服务器,以避免刷新页面时出现 404 错误。
Nginx 解决 404 问题
修改nginx.conf配置文件,在location下新增
location / {
try_files $uri $uri/ /index.html;
}命名路由
除了 path 之外,还可以为任何路由提供 name。这有以下优点:
- 没有硬编码的
URL params的自动编码/解码。- 防止你在
URL中出现打字错误。 - 绕过路径排序(如显示一个) 在使用
router-link组件to属性传递一个对象
<router-link :to="{ name: 'user', params: { username: 'erina' }}"></router-link>路由懒加载
将路由组件按需加载,而不是一次性将所有路由组件都加载到初始页面中,把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。这样可以减少初始加载时的资源体积,提高应用程序的加载速度,并减轻初始负载,是 Vue 性能优化的一种
vue-router支持动态 import ,所有被导入的模块,在加载时就被编译。
// import Home from ('../Home.vue')
// import Login from ('../Login.vue')
const Home = () => import('../Home.vue');
const Login = () => import('../Login.vue');编程式导航
除了<router-link>标签来定义导航链接,还可以通过router实例上的方法来跳转。通过触发某个事件,执行route.push。
<template>
<div @click="gotoHome">User</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const route = useRouter();
function gotoHome() {
route.push('/user');
}
</script>该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串路径
router.push('/users/eduardo');
// 带有路径的对象
router.push({ path: '/users/eduardo' });
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } });
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } });
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' });路由传参
和之前的使用一样,可以通过query、parmas来传递参数
1.query
在使用编程式导航时,传入一个query对象为参数
function gotoHome(id) {
route.push({
path: '/user',
query: {
userId: id,
},
});
}使用userRoute里的query来接收参数
少了个 r, 和路由跳转 useRouter 不一样,userRoute 主要用来获取路由信息。
import { useRoute } from 'vue-router';
const route = useRoute();
console.log('user id', route.query.userId);2.params
在使用编程式导航时,传入一个params对象为参数,此时只能使用命名路由name。
function gotoHome(id) {
route.push({
name: 'user',
params: {
userId: id,
},
});
}使用params接收参数
import { useRoute } from 'vue-router';
const route = useRoute();
console.log('user id', route.params.userId);3.动态路由
可以在路由URL后面拼接数据,达到传递参数的目的。在定义路由的时候,就要在path后定义路径参数。
路径参数用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中。
ts
体验AI代码助手
代码解读
复制代码import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import ('../Home.vue')
},
{
path: '/user/:userId',
name: 'user',
component: () => import ('../User.vue')
}
]
})
export default router路由跳转和params方式一样
function gotoHome(id) {
route.push({
name: 'user',
params: {
userId: id,
},
});
}接收参数也是
import { useRoute } from 'vue-router';
const route = useRoute();
console.log('user id', route.params.userId);4.query 和 params 传参的区别
query传参数配置的是path,而params传参配置的是name,在params中配置path无效query在路由上可以不设置参数,而parmas是必须要设置query传递的参数会在URL地址栏上显示params参数在刷新页面后消失,query传递的参数不会。query方式通过route.query方法获取参数,parmas通过route.params方法获取
历史记录
1.replace
replace方法用于导航到一个新的路由,但是它会替换掉当前的历史记录,而不会生成新的历史记录。
在使用replace方法后,用户将无法通过浏览器的"后退"按钮返回到之前的页面,而是直接返回到上一个历史记录的前一步。
<router-link replace to="/login">
Go to Home
</router-link>;
// 将push替换成replace
route.replace('/login');2.前进后退
const next = () => {
// 前进,数量不限于1
router.go(1)
}
const prev = () => {
// 后退
router.back()嵌套路由
使用嵌套路由可以在一个父路由下定义多个子路由,使用children数据来接收子路由
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('../Home.vue'),
},
{
path: '/user',
name: 'user',
component: () => import('../User.vue'),
children: [
{
path: '', // 空路径表示父路由的默认子路由
component: () => import('../User.vue'),
},
{
path: 'profile',
component: () => import('../UserProfile.vue'),
},
{
path: 'posts',
component: () => import('../UserPosts.vue'),
},
],
},
],
});
export default router;children 配置只是另一个路由数组,就像 routes 本身一样。因此,可以根据自己的需要,不断地嵌套视图。
在组件中,也是要加上<router-view></router-view>
命名视图
命名视图可以将多个组件同时渲染到同一个路由的不同位置的方法,命名视图适用于一些布局较复杂的场景,比如同时在页面的顶部和底部显示不同的内容。可以为每个位置创建一个命名视图,然后在路由配置中根据需要将组件渲染到相应的位置。
定义路由的时候,在components中定义多个组件
import { createRouter, createWebHistory } from 'vue-router';
import MainLayout from './components/MainLayout.vue';
import HeaderComponent from './components/HeaderComponent.vue';
import ContentComponent from './components/ContentComponent.vue';
import FooterComponent from './components/FooterComponent.vue';
const routes = [
{
path: '/',
components: {
default: MainLayout,
header: HeaderComponent,
content: ContentComponent,
footer: FooterComponent,
},
},
// 其他路由配置...
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;在组件上使用<router-view>时增加name属性,对应路由中多个组件名
<template>
<div>
<!-- 使用命名视图渲染 Header、Content 和 Footer -->
<router-view name="header"></router-view>
<router-view name="content"></router-view>
<router-view name="footer"></router-view>
</div>
</template>重定向 redirect
重定向用于在用户访问某个路由时将其自动重定向到另一个路由。
import { createRouter, createWebHistory } from 'vue-router';
import HomeComponent from './components/HomeComponent.vue';
import AboutComponent from './components/AboutComponent.vue';
import ContactComponent from './components/ContactComponent.vue';
const routes = [
{
path: '/',
component: HomeComponent,
},
{
path: '/about',
component: AboutComponent,
},
{
path: '/contact',
component: ContactComponent,
// 设置重定向
redirect: '/about', // 将访问 '/contact' 重定向到 '/about'
},
// 其他路由配置...
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;路由守卫
路由守卫是vue-router提供的一种特性,允许开发者在路由切换的过程中添加一些自定义的逻辑。这些逻辑可以用于控制路由导航的行为,例如在用户访问特定路由前进行身份验证、权限检查、处理未保存的表单数据等。
主要分为三种类型路由守卫:全局守卫、组件独享守卫、组件内守卫
每个守卫方法接收以下参数:
to: 要进入的目标路由对象from:当前导航正要离开的路由对象next可选:一个函数,用于决定是否允许路由切换,调用该函数时可以传递一个参数指定要跳转的路由路径。它有三种调用方式。next():通过当前路由进入到下一个路由next(false):中断当前路由next('/login')或者next({path: '/login'}):重定向到其他路由。
可以返回的值如下:
false: 取消当前的导航。如果浏览器的URL改变了(可能是用户手动或者浏览器后退按钮),那么URL地址会重置到 from 路由对应的地址。- 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像调用
router.push()一样,你可以设置诸如replace: true或name: 'home'之类的配置。当前的导航被中断,然后进行一个新的导航,就和from一样。
router.beforeEach(async (to, from) => {
if (
// 检查用户是否已登录
!isAuthenticated &&
// ❗️ 避免无限重定向
to.name !== 'Login'
) {
// 将用户重定向到登录页面
return { name: 'Login' };
}
});全局守卫
- 全局前置守卫 beforeEach
使用router.beforeEach 注册一个全局前置守卫,该钩子在每次路由切换前调用,可以用于进行全局的身份验证或权限检查
- beforeResolve
使用router.beforeResolve 注册一个全局解析守卫,该钩子在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用。
afterEach
使用router.afterEach 注册一个全局后置守卫,该钩子在每次路由切换后调用,可以用于执行一些全局的清理任务或者数据统计等。
组件独享守卫 beforeEnter
组件独享守卫只会在特定路由配置中生效,影响到某个特定路由及其子路由。
使用router.beforeEnter 注册一个全局后置守卫,该钩子在进入路由时触发,用于对该路由进行独立的身份验证或其他检查。
组件内守卫
组件内守卫在路由组件内直接定义路由导航守卫
- beforeRouteEnter
该钩子在渲染该组件的对应路由被验证前调用,不能获取组件实例 this。
不能访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
解决办法
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}- beforeRouteUpdate
该钩子在当前路由改变,但是该组件被复用时调用
- beforeRouteLeave
该钩子在导航离开渲染该组件的对应路由时调用
- 在 setup 中使用
用vue3组合式 API 来编写组见,可以通过onBeforeRouteUpdate 和 onBeforeRouteLeave 分别添加 update 和 leave 守卫。
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';
// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from, next) => {
next((vm) => {
// 通过 `vm` 访问组件实例
});
});
onBeforeRouteUpdate(async (to, from) => {});完整的路由导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在重用的组件里调用
beforeRouteUpdate守卫(2.2+)。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由元信息
路由元信息是在 Vue Router 中用于给路由添加额外信息的一种机制。它允许在路由配置中定义自定义的数据字段,用于存储一些与路由相关的元数据,例如页面标题、权限要求、面包屑等。
每个路由配置对象可以包含一个名为meta的字段,该字段是一个对象,用于存储路由的元信息。这些信息可以在导航守卫、路由组件或其他地方进行访问,从而允许你在应用程序中根据路由的元信息来做出不同的处理。
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false }
}
]
}
]要访问路由的元信息,可以在导航守卫中使用to对象来获取。例如,在全局前置守卫beforeEach中可以这样访问:
router.beforeEach((to, from, next) => {
// 获取目标路由的元信息
const requiresAuth = to.meta.requiresAuth;
// 在这里进行一些逻辑判断,例如根据requiresAuth决定是否需要登录验证
// 继续路由导航
next();
});而在组件中,可以通过route.meta来访问
import { useRoute } from 'vue-router';
const route = useRoute();
console.log('user id', route.query.meta.requiresAuth);动态路由
通常在后台项目中,一般都是用动态路由,也就是路由列表由后台返回,前端拿到数据后动态的添加路由。这样做的好处是可以做到权限控制,不同的角色权限访问不同的内容。
主要使用的方法是router.addRoute
添加路由
使用router.addRoute来注册一个路由
router.addRoute({ path: '/login', component: Login });删除路由
通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
tsrouter.addRoute({ path: '/about', name: 'about', component: About }); // 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的 router.addRoute({ path: '/other', name: 'about', component: Other });通过调用
router.addRoute()返回的回调:
tsconst removeRoute = router.addRoute(routeRecord); removeRoute(); // 删除路由如果存在的话调用
router.removeRoute按名称删除路由
tsrouter.remove('login');
当路由删除时,所有的子路有都会被删除
查看路由
router.hasRoute('login'):检查路由是否存在。router.getRoutes():获取一个包含所有路由记录的数组。