路由
- 前端路由指的是一种将浏览器 URL 与特定页面或视图关联起来的技术。在传统的 Web 开发中,当用户点击链接或者输入 URL 时,服务器会接收到请求并返回相应的 HTML 页面。而在前端路由中,当用户点击链接或者输入 URL 时,浏览器会根据路由规则对 URL 进行解析,并使用 JavaScript 控制页面的展示。
- 前端路由通常使用 JavaScript 库来实现,比如 React Router、Vue Router 等。它们允许开发者定义路由规则,并根据这些规则来显示不同的组件或页面。例如,当用户点击一个链接时,前端路由会将 URL 解析为一个路由路径,然后根据路径匹配相应的组件或页面并显示在页面上,而不需要向服务器发起请求。
- 前端路由可以提高 Web 应用的性能和用户体验,因为它允许应用实现快速的页面切换和动态的内容加载,同时减少了服务器的负载。
安装
- 因为是借助 JS 库来实现的, 所以使用前必须先安装对应的库
- 指令:
npm i vue-router
路由模式
1. hash 模式
使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载,其显示的网路路径中会有 “#” 号,有一点点丑。比如 http://example.com/#/about。这是最安全的模式,因为他兼容所有的浏览器和服务器。
2. history 模式
依赖于 Html5 的 history,pushState API,所以要担心 IE9 以及一下的版本。并且还包括 back、forward、go 三个方法,对应浏览器的前进,后退,跳转操作。就是浏览器左上角的前进、后退等按钮进行的操作
3. abstract 模式(了解)
abstract 是 vue 路由中的第三种模式,本身是用来在不支持浏览器 API 的环境中,充当 fallback,而不论是 hash 还是 history 模式都会对浏览器上的 url 产生作用,本文要实现的功能就是在已存在的路由页面中内嵌其他的路由页面,而保持在浏览器当中依旧显示当前页面的路由 path,这就利用到了 abstract 这种与浏览器分离的路由模式
定义路由表
1. 在 src 下创建 router 目录, 并在目录里创建 index.js 文件
import { createRouter, createWebHashHistory } from "vue-router";
// 使用 createRouter 创建路由实例
const router = createRouter({
// 确定路由模式,当前使用hash模式
history: createWebHashHistory(),
// 定义路由表
routes: [
{
// 路由地址
path: "/a",
// 对应路由显示组件
component: () => import("../a.vue"),
},
],
});
// 导出
export default router;
2. 在入口文件引入,并挂载
import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
// 使用 use方法挂载到vue上
app.use(router);
app.mount("#app");
3. 在跟组件上使用 router-view 展示路由
<template>
<div>
<router-view></router-view>
</div>
</template>
4. 查看页面
页面中使用路由
页面中进行路由跳转
1. 普通跳转
<template>
<div>
<button @click="linkB">跳转到B页面</button>
</div>
</template>
<script>
export default {
methods: {
linkB() {
/**
* 使用 this.$router对象里的push方法
*
* 接收一个路由地址作为参数
* 跳转到b页面
*/
this.$router.push("/b");
},
},
};
</script>
2. 替换当前页面
<template>
<div>
<button @click="linkB">跳转到B页面</button>
</div>
</template>
<script>
export default {
methods: {
linkB() {
/**
* 使用 this.$router对象里的replace方法
*
* 接收一个路由地址作为参数
*
* 从 a 页面 替换到 b 页面
* 不会再历史记录中新增数据
*/
this.$router.replace("/b");
},
},
};
</script>
3. 前进或后退到浏览器历史记录中
<template>
<div>
<button @click="linkB">跳转到B页面</button>
</div>
</template>
<script>
export default {
methods: {
linkB() {
/**
* 后退一页
*/
this.$router.go(-1);
},
},
};
</script>
路由传参
params 传参
需要在路由表中定义,参数使用/进行拼接,定义后默认为必传参数
定义
跳转到 b 页面需要携带 id 和 name 两个参数
import { createRouter, createWebHashHistory } from "vue-router";
/**
* 使用 createRouter 创建路由实例
*/
const router = createRouter({
// 确定路由模式,当前使用hash模式
history: createWebHashHistory(),
/**
* 定义路由表
*/
routes: [
{
// 路由地址
path: "/a",
// 对应路由显示组件
component: () => import("../a.vue"),
},
{
// 跳转到b页面需要携带 id 和 name两个参数
// 如果参数不是必传,可以在参数后加?
// 注意:必传参数一定要在非必传参数前面
path: "/b/:id/:name",
// 对应路由显示组件
component: () => import("./../b.vue"),
},
],
});
/**
* 导出
*/
export default router;
跳转并传递参数
我们定义了一个名为 id 的参数,它可以在 URL 中的 /:id 部分找到。当用户访问 /users/123 时,123 将成为路由参数
<template>
<div>
<button @click="linkB">跳转到B页面</button>
</div>
</template>
<script>
export default {
methods: {
linkB() {
/**
* 使用/拼接
* 参数的顺序和路由表定义的顺序一致
*
*/
// this.$router.push('/b/1/张三')
/**
* 如果参数非常多那么也可以直接传入一个params对象
*/
this.$router.push({
// 注意:要在这里定义路由的名字
name: "b",
/**
* 要传的参数
* 会自动拼接在url上
*/
params: {
name: "张三",
id: 1,
},
});
},
},
};
</script>
接收参数
<template>
<div>b 页面</div>
</template>
<script>
export default {
created() {
/**
* 使用 $route 的 params 对象接收
*/
console.log(this.$route.params);
},
};
</script>
query 传参
参数使用 ? 进行拼接,参数之间用 & 隔开,不需要再路由表中定义
跳转
<template>
<div>
<button @click="linkB">跳转到B页面</button>
</div>
</template>
<script>
export default {
methods: {
linkB() {
/**
* 使用?进行拼接参数之间用&隔开
* 类似于get请求参数
*/
// this.$router.push('/b?id=1&name=张三')
/**
* 如果参数非常多那么也可以直接传入一个query对象
*/
this.$router.push({
// 路由路径
path: "/b",
/**
* 要传的参数
* 会自动拼接在url上
*/
query: {
name: "张三",
id: 1,
},
});
},
},
};
</script>
接收参数
<template>
<div>b 页面</div>
</template>
<script>
export default {
created() {
/**
* 使用 $route 的 query 对象接收 query形式的传参
*/
console.log(this.$route.query);
},
};
</script>
嵌套路由
点击顶栏导航按钮。页面跳转。导航栏保持不变。页面改变
- 路由表中定义
import { createRouter, createWebHashHistory } from "vue-router";
/**
* 使用 createRouter 创建路由实例
*/
const router = createRouter({
// 确定路由模式,当前使用hash模式
history: createWebHashHistory(),
/**
* 定义路由表
*/
routes: [
{
// 路由地址
path: "/a",
// 对应路由显示组件
component: () => import("./../a.vue"),
/**
* 使用 children 定义当前路由下的子路由
* children 是一个 数组 写法和上面几乎一致
*/
children: [
{
path: "/a1",
component: () => import("./../a1.vue"),
},
{
path: "/a2",
component: () => import("./../a2.vue"),
},
],
},
{
// 跳转到b页面需要携带 id 和 name两个参数
path: "/b/:id/:name",
// 对应路由显示组件
component: () => import("./../b.vue"),
},
],
});
/**
* 导出
*/
export default router;
- 在 A 页面中展示对应子路由
<template>
<div>
<router-link to="/a1">a1</router-link>
<router-link to="/a2">a2</router-link>
<!-- 在a页面中使用 router-view 展示对应子路由-->
<router-view></router-view>
</div>
</template>
<script>
export default {
methods: {
linkB() {
/**
* 使用?进行拼接参数之间用&隔开
* 类似于get请求参数
*/
this.$router.push("/b?id=1&name=张三");
},
},
};
</script>
路由守卫
- 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
- 简单理解:导航守卫就是路由跳转过程中的一些钩子函数,再直白点路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,比如跳转前是否验证登录等,这就是导航守卫。
全局守卫
是指路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数。钩子函数按执行顺序包括 beforeEach、beforeResolve、afterEach 三个
beforeEach
在路由跳转前触发,参数包括 to,from,next(参数会单独介绍)三个,这个钩子作用主要是用于登录验证,也就是路由还没跳转提前告知,以免跳转了再通知就为时已晚
router.beforeEach((to, from, next) => {
// to 将要访问的路径
// from 代表从哪个路径跳转而来
// next 是一个函数,表示放行 使用后 beforeEach 必须得调用next
// next() 放行 next('/login') 强制跳转
});
beforeResolve
- 这个钩子和 beforeEach 类似,也是路由跳转前触发,参数也是 to, from, next 三个,和 beforeEach 区别官方解释为:
- 区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
- 即在 beforeEach 和 组件内 beforeRouteEnter 之后,afterEach 之前调用
router.beforeResolve((to, from, next) => {
// to 将要访问的路径
// from 代表从哪个路径跳转而来
// next 是一个函数,表示放行 使用后beforeEach 必须得调用next
// next() 放行 next('/login') 强制跳转
});
afterEach
和 beforeEach 相反,他是在路由跳转完成后触发,参数包括 to, from 没有了 next(参数会单独介绍),他发生在 beforeEach 和 beforeResolve 之后,beforeRouteEnter (组件内守卫)之前
router.afterEach((to, from) => {
// to 将要访问的路径
// from 代表从哪个路径跳转而来
});
beforeEnter 单个路由独享
beforeEnter 在路由配置中定义的钩子函数,它会在路由被激活之前调用。它和全局前置守卫的参数一样,但是只对该路由生效。
import { createRouter, createWebHashHistory } from "vue-router";
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/a",
component: () => import("./../a.vue"),
beforeEnter: (to, from, next) => {
next();
},
children: [
{
path: "/a1",
component: () => import("./../a1.vue"),
},
{
path: "/a2",
component: () => import("./../a2.vue"),
},
],
},
{
path: "/b/:id/:name",
component: () => import("./../b.vue"),
},
],
});
export default router;
组件内守卫
是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。钩子函数按执行顺序包括 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave 三个
beforeRouteEach
路由进入之前调用,参数包括 to,from,next。该钩子在全局守卫 beforeEach 和独享守卫 beforeEnter 之后,全局 beforeResolve 和全局 afterEach 之前调用,要注意的是该守卫内访问不到组件的实例,也就是 this 为 undefined,也就是他在 beforeCreate 生命周期前触发。在这个钩子函数中,可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数,可以在这个守卫中请求服务端获取数据,当成功获取并能进入路由时,调用 next 并在回调中通过 vm 访问组件实例进行赋值等操作,(next 中函数的调用在 mounted 之后:为了确保能对组件实例的完整访问)
beforeRouteEnter (to, from, next) { // 这里还无法访问到组件实例,this === undefined
next( vm => {
// 通过 `vm` 访问组件实例
})
}
beforeRouteUpdate
- 在当前路由改变时,并且该组件被复用时调用,可以通过 this 访问实例。参数包括 to,from,next。
- 对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,组件实例会被复用,该守卫会被调用 当前路由 query 变更时,该守卫会被调用。
beforeRouteUpdate (to, from) {}
beforeRouteLeave
- 导航离开该组件的对应路由时调用,可以访问组件实例 this,参数包括 to,from,next。只有调用 next 才可以跳转
beforeRouteLeave (to, from, next) {}
路由懒加载
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效
// 将 import UserDetails from './views/UserDetails.vue' 替换成下边的写法
const UserDetails = () => import("./views/UserDetails.vue");
const router = createRouter({
// ...
routes: [{ path: "/users/:id", component: UserDetails }],
});
动态路由
对路由的添加通常是通过 routes 选项来完成的,但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由。动态路由主要通过两个函数实现。router.addRoute() 和 router.removeRoute()。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由
1. 添加路由
// 接收一个参数是路由对象
router.addRoute({ path: "/about", component: About });
/**
* 添加嵌套路由
* 第一个参数是父级路由的name
* 第二个参数 是路由对象
*/
router.addRoute("父级路由Name", { path: "/about", component: About });
2. 删除路由
// 传入要删除路由的name
router.removeRoute("about");