路由其实是网络工程中的一个术语:
映射表
;路由的概念在软件工程中出现,最早是在后端路由中实现的,原因是web的发展主要经历了这样一些阶段:
早期的网站开发整个HTML页面是由服务器来渲染的.
但是, 一个网站, 这么多页面服务器如何处理呢?
上面的这种操作, 就是后端路由:
有利于SEO的优化
.后端路由的缺点:
前端渲染的理解:
从静态资源服务器请求文件
;前后端分离阶段:
前后端分离
的开发模式;单页面富应用阶段:
前端路由
.前端路由的核心是什么呢?改变URL,但是页面不进行整体的刷新。
前端路由是如何做到URL和内容进行映射呢?监听URL的改变。
URL的hash
URL的hash也就是锚点(#), 本质上是改变window.location的href属性;
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新;
<div id="app">
<a href="#/home">homea>
<a href="#/about">abouta>
<div class="router-view">div>
div>
<script>
// 1.获取router-view
const routerViewEl = document.querySelector(".router-view")
// 2.监听hashchange
window.addEventListener("hashchange", () => {
switch(location.hash) {
case '#/home':
routerViewEl.innerHTML = "home";
break;
case '#/about':
routerViewEl.innerHTML = "about";
break;
}
})
script>
hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#
,显得不像一个真实的路径。
history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:
replaceState
:替换原来的路径;pushState
:使用新的路径;popState
:路径的回退;go
:向前或向后改变路径;forward
:向前改变路径;back
:向后改变路径;//1.获取router-view
const routerViewEl = document.querySelector(".router-view");
//2.监听所有的a元素
const aEls = document.getElementsByTagName("a")
for (let aEl of aEls){
aEl.addEventListener("click",·(e) =>{
e.preventDefault();
const href = aEl.getAttribute("href");
console.log(href);
history.pushState({},"",·href);
historyChange();
})
}
//·3.监听popstate和go操作
window.addEventListener("popstate",·historyChange);
window.addEventListener("go",·historyChange);
//4.执行设置页面操作
function historyChange(){
switch(location.pathname){
case "/home":
routerViewEl.innerHTML="home";
break;
case "/about":
routerViewEl.innerHTML ="about";
break;
default:
routerViewEl.innerHTML·="default";
}
}
目前前端流行的三大框架, 都有自己的路由实现:
Vue Router 是 Vue.js 的官方路由:
单页应用
(SPA)变得非常容易;vue-router是基于路由和组件的
安装Vue Router
npm install vue-router
使用vue-router的步骤:
createRouter
创建路由对象,并且传入routes和history模式;
和
;router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
// 导入创建的组件
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
// 配置路由映射
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About },
]
// 创建router对象
const router = createRouter({
routes,
history: createWebHashHistory()
})
export default router
main.js
import router from './router'
// 使用router
createApp(App).use(router).mount('#app')
App.vue
<template>
<div>
<router-link to="/home">首页router-link>
<router-link to="/about">关于router-link>
<router-view>router-view>
div>
template>
我们这里还有一个不太好的实现:
如何可以让路径默认跳到到首页, 并且
渲染首页组件呢?
const routes = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/about', component: About },
]
我们在routes中又配置了一个映射:
redirect
是重定向, 也就是我们将根路径重定向到/home的路径下, 这样就可以得到我们想要的结果了.另外一种选择的模式是history模式:
import { createRouter, createWebHistory } from 'vue-router'
// 创建router对象
const router = createRouter({
routes,
history: createWebHistory()
})
router-link事实上有很多属性可以配置:
to
属性:
replace
属性:
active-class
属性:
exact-active-class
属性:
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载:
不同路由对应的组件分割成不同的代码块
,然后当路由被访问的时候才加载对应组件,这样就会更加高效;提高首屏的渲染效率
;其实这里还是我们前面讲到过的webpack的分包知识,而Vue Router默认就支持动态来导入组件:
Promise
;import函数
就是返回一个Promise;const routes = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: () => import('./Home.vue') },
{ path: '/about', component: () => import('./About.vue') },
]
我们看一下打包后的效果:
我们会发现分包是没有一个很明确的名称的,其实webpack从3.x开始支持对分包进行命名
(chunk name):
const routes = [
{ path: '/about', component: () => import(/* webpackChunkName: "about-chunk" */'./About.vue') },
]
name属性:路由记录独一无二的名称;
meta属性:自定义的数据
const routes = [{
path: '/about',
name: 'about-router',
component: () => import('./About.vue'),
meta: {
name: 'zhangsan',
age: 18
}
}]
很多时候我们需要将给定匹配模式的路由映射到同一个组件:
动态字段
来实现,我们称之为路径参数
;const routes = [{
path: '/user/:id',
component: () => import('./User.vue')
}]
在router-link中进行如下跳转:
<router-link to="/user/123">用户:123router-link>
那么在User中如何获取到对应的值呢? 在template中,直接通过 $route.params
获取值;
useRoute
;
<template>
用户界面:{{ $route.params.id }}
template>
import { useRoute } from 'vue-route'
export default {
setup() {
const route = useRoute()
console.log(route.params.id)
},
create() {
console.log(this.$route.params.id)
}
}
对于哪些没有匹配到的路由,我们通常会匹配到固定的某个页面
const routes = [{
path: '/:pathMatch(.*)',
component: () => import('./NotFound.vue')
}]
我们可以通过 $route.params.pathMatch获取到传入的参数:
<h2>
Not Found: {{ $route.params.pathMatch }}
h2>
这里还有另外一种写法:
*
;const routes = [{
path: '/:pathMatch(.*)*',
component: () => import('./NotFound.vue')
}]
它们的区别在于解析的时候,是否解析 /
:
什么是路由的嵌套呢?
但是呢,我们Home页面本身,也可能会在多个组件之间来回切换:
router-view
来占位之后需要渲染的组件;const routes = [{
name: "home",
path: "/home",
component: () => import("../Views/Home.vue"),
children: [
{
path: "",
redirect: "/home/recommend"
},
{
path: "recommend", // /home/recommend
component: () => import("../Views/HomeRecommend.vue")
},
{
path: "ranking", // /home/ranking
component: () => import("../Views/HomeRanking.vue")
}
]
}]
有时候我们希望通过代码来完成页面的跳转,比如点击的是一个按钮:
function jumpToProfile() {
this.$router.push('/profile')
}
当然,我们也可以传入一个对象:
function jumpToProfile() {
this.$router.push({
path: '/profile'
})
}
如果是在setup中编写的代码,那么我们可以通过 useRouter
来获取:
const router = useRouter()
function jumpToProfile() {
router.replace('/profile')
}
我们也可以通过query的方式来传递参数:
function jumpToProfile() {
this.$router.push({
path: '/profile',
query: { name: 'abc', age: 12 }
})
}
在界面中通过 $route.query
来获取参数:
<h2>
query: {{ $route.query.name }}
h2>
使用push的特点是压入一个新的页面,那么在用户点击返回时,上一个页面还可以回退,但是如果我们希望当前页面是一个替换 操作,那么可以使用replace:
声明式 | 编程式 |
---|---|
|
router.replace('...') |
router的go方法:
//·向前移动一条记录,与·router.forward()·相同
router.go(1)
//·返回一条记录,与router.back()·相同
router.go(-1)
//前进·3·条记录
router.go(3)
//如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
router也有back:
router也有forward:
某些情况下我们可能需要动态的来添加路由:
不同的权限
,注册不同的路由
;addRoute
;如果我们是为route添加一个children路由,那么可以传入对应的name:
// 动态添加一个路由
const categoryRoute = {
path: '/category',
component: () => import('../pages/Category.vue')
}
router.addRoute(categoryRoute)
const homeMomentRoute = {
path: '/moment',
component: () => import('../pages/HomeMoment.vue')
}
router.addRoute('home', homeMomentRoute)
删除路由有以下三种方式:
// 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
router.addRoute({ path: '/about', name: 'about', component: About })
// 删除路由
router.removeRoute('about')
// 删除路由如果存在的话
const·removeRoute = router.addRoute(routeRecord)
removeRoute()
路由的其他方法补充:
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航
。
全局的前置守卫beforeEach
是在导航触发时会被回调的:
它有两个参数:
to
:即将进入的路由Route对象;from
:即将离开的路由Route对象;router.beforeEach( (to, from) => {
log(to)
return false
})
它有返回值:
可选的第三个参数:next(不推荐使用)
比如我们完成一个功能,只有登录后才能看到其他页面
router/index.js
router.beforeEach((to, from) => {
const token = localStorage.getItem("token")
if (token || to.path === '/login') {
return true
}
return {
path: '/login',
query: {
refer: to.path
}
}
})
login.vue
import { useRoute, useRouter } from 'vue-router';
const route = useRoute()
const router = useRouter()
function login() {
localStorage.setItem("token", "zhangsan")
const refer = route.query.refer
console.log(refer)
if (refer) {
router.push(refer)
}
}
Vue还提供了很多的其他守卫函数,目的都是在某一个时刻给予我们回调,让我们可以更好的控制程序的流程或者功能:
我们一起来看一下完整的导航解析流程: