注意利用了动态组件的思路,即
标签,实现组件的动态切换,配合onhashchange
事件对hasx地址改变监听,实现路由跳转
当点击不同的a
链接的时候,对应的url地址的hash值就会跟着变化,同时事件监听到就执行对应的操作。
<template>
<div class="App-container">
<h1>这是App组件h1>
<a href="#/home">首页a>
<a href="#/body">主体a>
<a href="#/footer">尾部a>
<hr />
<component :is="componentId">component>
div>
template>
export default {
name: "App",
data() {
return {
componentId: "Home", //默认显示Home组件
};
},
components: { Home, Body, Footer },
mounted() {
window.onhashchange = () => {
switch (location.hash) {
case "#/home":
this.componentId = "Home";
break;
case "#/body":
this.componentId = "Body";
break;
case "#/footer":
this.componentId = "Footer";
break;
}
};
},
};
vue的插件库,专门用来实现SPA应用,即单个页面的应用。整个应用只有一个完整的页面。数据只是局部刷新,不会引起整个页面的刷新跳转。所以的数据都是通过ajax请求获取来的。
在之前的网页中实现路由功能是通过多页面的方式,即存在多个HTML后缀的文件,通过点击链接去实现不同页面的切换,但是这种切换路由的方法有一个缺点,就是整个页面都会被刷新,重新渲染,会造成页面的抖动。而在现在的路由中是单页面实现跳转。
一个路由route对应一个路径,完整对应的事情。
vue-router是Vue提供专门处理路由的一个插件库,需要npm i vue-router@3
安装vue2对应的才能使用的vue-router版本,默认安装vue3对应的vue-router4版本的。当安装完毕后,需要在入口文件中使用Vue.use(vue-router)
使用路由。同时也需要配置vue-router相关的配置项。
// 引入vue-router插件
import VueRouter from 'vue-router'
// 使用插件库
Vue.use(VueRouter)
当引入了vue-router插件,并使用了该插件的时候,vue就为我们提供了一个全新的配置项router,该配置项在new Vue()的时候配置。
router配置项不向vuex中的store一样可以随意配置不报错,如下,给router配置项设置了一个字符串作为值,vue开发者工具直接给出报错提示,即router必须是一个函数,所以我们需要单独创建一个文件实现router函数的功能。
以下是配置的路由文件,引入vue-router模块,即VueRouter,本质是一个构造函数,需要new实例化并配置路由的相关参数。
在VueRouter配置中提供了routes配置项,该配置项就是实现前端路由的主要参数。routes参数接收一个数组作为参数,数组的每一个参数均为一个路径配置对象,每一个配置对象可以设置path配置路径信息,当匹配成功的时候,就会渲染component配置项提供法组件,并去渲染。
import VueRouter from 'vue-router'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
// 配置路由为一个数组对象,每一个对象包含配置项
const router = new VueRouter({
routes: [
{
path: '/About',
component: About
},
{
path: '/Home',
component: Home
}
]
})
export default router
当配置完这些步骤后,同样的还需要在HTML结构文件中设置vue-router提供的语法,将原先页面中a标签的调整改成如下代码
其中将所有的a标签改成vue-router提供的全新标签router-link,并将原先href实现跳转的属性改成to。这样子一个基本的路由就实现了,为了效果的完美实现,将active动态的提交给路由控制,即利用active-class动态的添加样式active。最终所有的router-link标签都会被解析成a标签显示,且都是由vue-router自动添加上了#符号。实现功能就和原先a链接中的href中手动添加#符号效果一致。
<a href="#/路径"></a>
<router-link to="/路径"></router-link>
<!-- 原始页面跳转 -->
<!-- <a class="list-group-item" href="./about.html">About</a>
<a class="list-group-item active" href="./home.html">Home</a>
-->
<!-- vue-router跳转 -->
<router-link class="list-group-item" active-class="active" to="/about"
>About</router-link>
<router-link class="list-group-item" active-class="active" to="/home"
>Home</router-link>
设置完毕后还需要设置路由匹配成功的时候,即当匹配到/about路由的时候,需要将对应的组件显示在对应的位置。为此vue-router提供了新的标签router-view,即将匹配到的组件信息放在该标签的地方显示。
<router-view></router-view>
这里配置的路由是前端路由,不涉及任何后端服务器,在浏览器的网络请求中就可以看出,没有任何请求发送。
路由的组件分类
*:一般的组件不需要经过路由配置处理显示的组件称为一般组件,即需要使用组件标签放在结构中的相关位置。通常这些组件是放在components文件夹中。
*:路由组件即需要通过vue-router管理的路由,通常需要放在一个与components文件夹同级别的pages文件夹中。路由组件最重要的特点是不需要亲自使用组件标签,而是通过配置属性后,由路由决定将哪些组件放再router-view标签中显示。
路由组件的销毁问题
*:所有通过路由管理的组件,当切换路由的时切换到其他组件的时候,原先组件默认是被销毁的(可以使用特殊方法),在成功配置路由的时候才会挂载。
在路由组件中配置如下代码查看生命周期函数
mounted() {
console.log("home创建了");
},
beforeDestroy() {
console.log("home被销毁了");
},
3: $route与$router属性
*:当成功配置完路由的时候,所有的路由组件实例都会多出两个属性,$route和$router。所有的路由组件中$route都是各不相同的。但是每一个$router都是相同的
在各个路由组件中配置如下代表即可验证
window.aboutRoute = this.$route;
window.aboutRouter = this.$router;
window.homeRoute = this.$route;
window.homeRouter = this.$router;
当某些时候访问到没有权限的路由的时候,会自动进行重定向操作。
如下代码中:当路径如http://www.localhost:8080的时候,会自动重定向到主页home。
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{
path: '/body',
component: Body,
redirect: '/body/item',
children: [
{ path: 'item', component: Item },
{ path: 'item2', component: Item2 },
]
},
{ path: '/footer', component: Footer },
]
在上诉代码中,在子路由中的重定向实现过程中,使用redirect
重定向过于麻烦,所以这里使用默认子路由实现。
默认子路由:如果在children
数组中,某个路由规则的path
值为空字符串,则这条路由规则叫做默认子路由。
如下代码,当匹配到/body
路由的时候,由于该路由的children配置项中存在默认子路由,所以又会去显示默认子路由中的组件信息。
<router-link to="/body">显示的内容1</router-link>
----------------------------
{
path: '/body',
component: Body,
children: [
{ path: '', component: Item },
{ path: 'item2', component: Item2 },
]
},
多级路由在原先的基础上多配置一个children配置项,即告诉路由器这是一个子路由,在子路由同样接收一个数组对象,其中每一个对象接收的参数和routes的配置项一样,最重要的一点就是子路由中的path不需要添加‘/’符号,这一点和express中的子路由匹配不同。这是因为vue-router在运行的时候,检索到children配置项就知道这是一个子路由,于是在底层就会自动为我们的path路由添加上‘/’符号,如果我们错误的操作添加了‘/’符号,vue-router在匹配时候就无法正确的匹配到路由。
const router = new VueRouter({
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
// 配置多级路由
children: [
{
path: 'news',
component: News
},
{
path: "messages",
component: Messages
}
]
}
]
})
在router-link中的to需要填写完整的路径信息,即匹配到一级路由后再次去匹配其子路由。
<template>
<div>
<h2>我是Home的内容</h2>
<ul class="nav nav-tabs">
<li>
<router-link
class="list-group-item"
active-class="active"
to="/home/news"
>News</router-link
>
</li>
<li>
<router-link
class="list-group-item"
active-class="active"
to="/home/messages"
>Message</router-link
>
</li>
</ul>
<router-view></router-view>
</div>
</template>
在填写路由路径的时候也可以使用’/:id’进行占位
动态路由指的是:把hash地址可变的部分作为参数项,提高路由规则的复用性。
使用:
作为占位符来接收这些动态的参数。
<router-link to="/footer/1">尾部</router-link>
<router-link to="/footer/2">尾部</router-link>
<router-link to="/footer/3">尾部</router-link>
----------------------------
//只需要定义一个/footer/:id即可实现规则
{ path: '/footer/:id', component: Footer },
在之前的代码基础上配置一个三级路由组件,在二级路由组件的基础上,将数据传递过去显示。
数据信息通过二级路由组件messages传递过去。
const router = new VueRouter({
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
// 配置多级路由
children: [
{
path: 'news',
component: News
},
{
path: "messages",
component: Messages,
// 再次配置三级路由
children: [
{
path: 'details',
component: Details
}
]
}
]
}
]
})
在路由传递过程中有多种携带数据的方式,这里先使用简单的query格式携带数据,即get默认的携带数据方式。
key=value&key-value
<router-link to="/home/messages/details?id=666&title=你好">{{ detail.title }}</router-link>
接收方如何接收传递来的数据,这就需要借助每一个路由组件身上都有的$route属性,且该属性是各不相同的。打印$route输出如下
当从二级路由携带参数跳转到三级路由的时候,在三级路由后打印其$route属性,其为一个对象,对应的path属性会接收路径信息(不带参数),其中query是接收传递来的参数,表现形式为一个对象。
接收方接收数据显示的代码
<ul>
<li>消息编号:{{ $route.query.id }}</li>
<li>消息标题:{{ $route.query.title }}</li>
</ul>
采用指令语法告诉vue这后面的代码需要执行JS语法,在字符串里面采取了模板字符串的语法
<router-link :to="`/home/messages/details?id=${detail.id}&title=${detail.title}`">
{{ detail.title }}
</router-link
当采用对象传递数据的时候,需要采用指令语法去控制,让vue指定如何处理字符串中的内容,其中对象中必须包含两个参数,path告诉vue-router需要跳转的地址,query参数也是一个对象,在对象中配置需要传递的参数
<router-link
:to="{
path: '/home/messages/details',
query: {
id: detail.id,
title: detail.title,
},
}"
>
{{ detail.title }}</router-link
>
命名路由是在路由配置项中给添加name属性,其作用是简化路由跳转的写法。
name配置项
当配置了name属性的时候,在实现路由跳转的时候就能极大的简化路径的写法
const router = new VueRouter({
routes: [
{
name: 'About',//name名字可以自定义
path: '/about',
component: About
},
{
path: '/home',
component: Home,
// 配置多级路由
children: [
{
path: 'news',
component: News
},
{
path: "messages",
component: Messages,
// 再次配置三级路由
children: [
{
name: 'Detail',
path: 'details',
component: Details
}
]
}
]
}
]
})
在跳转三级路由的地方配置了name属性“Detail”,那么在需要匹配该路径且跳转到该组件的地方,都可以使用简写的方式。即在router-link中的to属性中添加name属性,即可跳转。当写这种方式的时候,只能写在指令语法to中
<router-link
:to="{
// path: '/home/messages/details',
name: 'Detail',
query: {
id: detail.id,
title: detail.title,
},
}"
>
{{ detail.title }}
</router-link>
在路由组件身上$route对象上,将参数全部存入params对象中,需要将在对应的路由组件中配置path路径中配置占位项,如/home/4,其中4就是通过占位符获取的数据。
当需要使用params传递参数的时候,在路径中需要进行修改,将需要传递的值直接拼接在路径后面,这里需要注意的是,原来的路径不能省略即那些一级路径至三级路径
当路由进行匹配的时候,匹配到details路径的时候会自动将后面的666与占位符:id组成一个对象中的属性即id:666,最终将这些属性存入params对象中
<router-link :to="`/home/messages/details/666/dds`">
{{ detail.title }}
</router-link>
当采用对象式传递params参数的时候如何写,只需要改动router-link中的即可
这里需要注意的是:当使用to对象格式传递params参数的时候需要将原先的query配置该为params配置,并且使用params的时候只能与name命名路由配合使用,无法与path参数配合使用,否则报错
<router-link
:to="{
name: 'Detail',
params: {
id: detail.id,
title: detail.title,
},
}"
>
{{ detail.title }}
</router-link>
//路由如下
<router-link to="/footer/1?name=zq">尾部</router-link>
引入路由参数rops的原因:当传递多个参数接收的时候,可能会重复的编写多个代码,这个时候就需要使用路由props配置项处理了。如下红色部分就是会重复多余的代码。
谁接收参数,就给哪一个路由组件添加props配置项。
props有三种写法
传递参数
props: {
id: '666',
title: 'Vue'
}
使用传递的参数
props: ["id", "title"],
路由配置
props: true
接收参数
props: ["id", "title"],
一定需要注意这种布尔值的配置形式不能处理query传递参数的情况
props($route) {
return { id: $route.query.id, title: $route.query.title }
}
接收并使用参数
props: ["id", "title"],
props($route) {
return { id: $route.params.id, title: $route.params.title }
}
当然,如果想代码更加精简,可以在接收$route的时候就对该对象进行解构赋值
replace作用:控制路由跳转时操作浏览器历史记录的模式。
如果不给router-link标签添加replace属性,则默认为push模式,即压栈,push是不断的往里面追加历史记录,而replace是替换历史记录。
:replace="true"
和replace
两种写法
上面使用
实现路由跳转的属于声明式路由导航,对标普通网页中的a
链接。
现在所学的路由跳转是通过router-link标签实现跳转的,并且该标签最终会转换为a标签。那么如何给其他标签绑定路由跳转。
这个时候就需要借助$router
属性了,该属性的原型链存放了帮助我们进行路由跳转的方法。
这里我们需要使用$router身上的push方法和replace方法
,其作用和在页面中使用router-link/to的作用是一致的,区别只是采取什么操作进行历史记录。在push/replace方法中,参数和to参数的写法一致
<button @click="pushShow(detail)">push操作</button>
<button @click="replaceShow(detail)">replace操作</button>
methods: {
pushShow(detail) {
this.$router.push({
name: "Detail",
params: {
id: detail.id,
title: detail.title,
},
});
},
replaceShow(detail) {
this.$router.replace({
name: "Detail",
params: {
id: detail.id,
title: detail.title,
},
});
},
},
push
和replace
的区别是:push
方法类似栈的结构,符合浏览器中历史回退的前进和后退操作。而replace
方法每次有新地址进入,都会覆盖上一个地址。
同时,在$router对象原型链上还有三个方法,分别为:back,forward,go
,其中back和forward方法实现的效果和浏览器的前进后退效果一致,且不需要传入参数。go方法需要指定传入的数字,正数代表前进几步,负数代表回退几步。
methods: {
forward() {
// 实现浏览器前进效果
this.$router.forward();
},
back() {
// 实现浏览器回退效果
this.$router.back();
},
go() {
this.$router.go(-2); //前进两下
},
},
作用:在切换组件的时候,让原先的组件保持挂载不被销毁。
使用keep-alive
标签**包住router-view
标签。**为什么需要包裹router-view
标签,是因为那些最终需要缓存的组件,都是被存放在该标签中显示的。
keep-alive
标签中的include
属性。如果不添加该属性,则默认在router-view
标签中的所有展示的组件,在切换的过程中均不会被销毁而是缓存,但是通常并不是所以的组件都需要被缓存。
当include="组件中的name值,即组件名"
指定值的时候,即指定了哪些组件才需要被缓存。
如果有多个组价,但是需要缓存其中的几个组件,就需要在include
属性后面赋数组作为值。同时需要使用指令语法,告诉vue后面字符串中的值需要按照JS的语法去解读。
<keep-alive :include="['News', 'Messages']">
<router-view></router-view>
</keep-alive>
使用keep-alive
标签的最大作用是:当有一些输入框的时候,在其中输入了值,但是不希望切换组件的时候这些内容被销毁了。这时候就需要借助keep-alive
标签缓存组件了。
作用:路由组件独有的生命周期函数,用于捕获路由组件的激活状态。
activated生命周期函数
和deactivated生命周期函数
activated
生命周期函数:用于组件被激活的时候触发
deactivated
生命周期函数:用于组件失活的时候触发
引入这两个新的生命周期钩子的原因是:在原先的基础中,将定时器存放在mounted
生命周期函数中,然而在路由组件的切换过程中会出现一个问题,就是当切换组件的时候,该组件被缓存了,随即定时器也会被暂停。但是这个效果在mounted
函数和beforeDestroy
生命周期函数中无法实现。因为这两个函数只针对组件被销毁,而缓存不属于这两个函数的范畴内。
以下是基于mouted
和beforeDestroy
生命周期函数实现的清除定时器效果,发现从News组件切回Messages组件,定时器无法清除
当使用activated
和deactivated
生命周期函数实现定时器效果就可以完成想要的结果
activated() {
this.timer = setInterval(() => {
console.log("@");
this.opacity -= 0.01;
if (this.opacity <= 0) {
this.opacity = 1;
}
}, 16);
},
deactivated() {
console.log("News销毁");
clearInterval(this.timer);
},
路由守卫的作用:在每一次切换路径的前后时间段,作出相应的操作。如路径判断是否放行等。
配置路由守卫应该是所有的路径都会进行相应的操作,所以需要放在router身上。
利用实例化完的vue-router,配置路由守卫。在router身上提供了beforeEach
方法,beforeEach
方法会在初始化的时候和路由切换前的时候执行,该方法接收三个参数分别为:to
,from
,next
,
to
:Router 即将进入的目标路由对象
from
: Router当前导航离开的路由,即上一个路由对象
next
:为函数,调用该方法,即可放行,类似express中的中间件next放行
const router = new VueRouter({ ... })
全局前置路由守卫
router.beforeEach((to, from, next) => {
console.log(to, from)
})
通过打印to
和from
发现结果如下,于是我们就可以利用里面的参数;来决定是否放行。
以下这段代码实现了如图所示的功能
// 配置全局前置路由守卫
router.beforeEach((to, from, next) => {
// 如果进入的是子路由查看详细信息,就进行本地存储判断
if (to.path === '/home/news' || to.path === '/home/messages') {
if (localStorage.getItem('name') === 'admin') {
next()
} else {
alert('错误信息!')
}
} else {
next()
}
})
但是这么写有一个问题,就是当路径判断变多的时候,在if
判断中就会写很长的代码,这不是理想的结果,所以,vue-router为我们提供了每一个路由都独有的meta
配置项,在该配置项中可以决定哪些路径是否进行权限控制。
提供meta
配置项的初衷是为了可以在路由中配置自己的信息,但是如果直接配置在配置对象中vue-router不会对该属性进行处理。所以meta
配置项的作用就体现出来。将所有自己的信息存入meta
元信息中保存。
全局后置路由守卫也是使用router
路由器提供的afterEach
方法。该方法在路由改变之后进行相应的操作,只要路由没有发生跳转就不会被执行。afterEach
方法只接收两个参数即to
和from
,(接收的参数和前置路由守卫一样),接收的参数数量和前置路由守卫不同,不需要next
的原因是,后置路由守卫在路由切换后才进行操作,这个时候就不需要next
函数进行放行操作了。
添加一个需求,即每次页面切换的时候,切换网页的标题。提前将自定义的数据存放在meta
元信息中。
在前置路由中配置代码解决此问题,但是还是会有一个问题,就是当页面在localhost:8080的时候,不断的刷新回发现标题会在vue_router_01test和首页之间来回切换。这个问题可以在index.html中修改默认的title显示。但是这种操作麻烦,所以全局后置路由就出现了。
router.beforeEach((to, from, next) => {
// 如果进入的是子路由查看详细信息,就进行本地存储判断
if (to.meta.isAuth) { //进行路由权限校验
/* 放在这里为时过早,如果本地存储判断失败则显示上一个的title,而
这里会提前显示 */
// document.title = to.meta.title
if (localStorage.getItem('name') === 'admin') {
document.title = to.meta.title
next()
} else {
alert('错误信息!')
}
} else {
document.title = to.meta.title || '首页' //初始化没有路径信息即title为undefined
next()
}
})
将所有对标题的操作全部放在后置路由中
// 配置全局前置路由守卫
router.beforeEach((to, from, next) => {
// 如果进入的是子路由查看详细信息,就进行本地存储判断
if (to.meta.isAuth) { //进行路由权限校验
if (localStorage.getItem('name') === 'admin') {
next()
} else {
alert('错误信息!')
}
} else {
next()
}
})
// 配置全局后置路由守卫
/* 当路由跳转失败的时候,后置路由不会走
所以页面标题就显示上一次的标题 */
router.afterEach((to, from) => {
console.log("后置")
document.title = to.meta.title || '首页'
})
独享路由守卫即局部的路由守卫,只针对某一个路径进行相应的操作。
独享路由守卫只有前置路由没有后置路由,这是一个重点需要记忆的地方。需要用到后置路由处理的东西全局后置路由即可完成效果。
beforeEnter
独享路由的守卫方法,该方法的作用是全局前置路由守卫作用一致,即在路径切换前进行处理,也是接收三个参数分别为to
,from
,next
。
顾名思义,即在组件内部使用路由守卫。
beforeRouterEnter(to,from,next)方法
,该方法当匹配路由规则后允许进入组件前调用。这个时候不能获取this实例,因为守卫执行的前,组件实例还没有被创建。
beforeRouterLeave(to,from,next)方法
,离开该组件的时候,该方法会被调用。这个时候可以访问this。
注意这里的组件内路由守卫和全局前置后置路由守卫的区别。在全局前置后置路由中,一旦匹配成功后,则全局前置与后置路由都会执行。而组件内的路由守卫不会,特别注意beforeRouterLeave方法,当匹配路由成功的时候进入该组件,只有beforeRouterEnter方法会被调用,当切换路由的时候从当前组件离开的时候,beforeRouterLeave方法才会被执行。
下图所示,当匹配路由规则进入后,只有beforeRouterEnter
方法执行。
当离开组件的时候进入其他组件,beforeRouterLeave
方法就会被调用
注意:匹配路由规则的含义,如果直接在页面中使用该组件的组件标签,则不属于路由范畴,所以并不会去执行beforeRouterEnter
方法。
在默认的服务器环境下,我们打开了某一个路径显示的页面,会发现在url中总是包裹着一个‘#’符号,这个就是前端所说的hash,一旦是hash模式,那么‘#’符号后面的所有路径信息不会出现在HTTP请求中,即不会被发送给服务器。
history模式,即不带’#'符号的正常路径
在vue-router中默认为hash模式,如果想修改为history模式,则需要在路由器的实例中设置配置项mode
,
mode
配置项的默认值为hash
,可以修改为history
。
那么hash模式和history模式的区别:首先history的路径看起来舒服,其次hash模式在低版本的浏览器中兼容性好。且涉及到项目打包上线服务器的相关操作使用hash模式方便。
浏览器只认识html,css,js文件,所有的vue编写的文件最终都需要打包才会生成浏览器能认识的文件。
这个时候package文件中提供的命名操作就起作用了。serve是运行脚手架环境的服务器,而build是将写好的vue文件打包为浏览器能认识的文件。
npm run build
当打包完成后,项目文件中会多次一个文件夹dist,其中存放这打包后的vue文件。但是个index.html文件是无法打开的,这些文件必须部署在服务器后才能打开
hash
的时候当vue-router的模式为hash
的时候,将生成的dist文件部署在服务器的静态文件中,查看效果,在index.html页面中点击任何按钮都不会发送网络请求。
hash模式下最大的特点是:当进入某一个路由的时候,地址如:http://localhost:5050#/home,一旦刷新页面,不会报错404,页面能够正常显示,(服务端设置的静态资源文件名index.html,localhost:5050默认打开该文件,前提文件名必须为index)这是因为#符号及其后面的内容都不会随着HTTP请求发送给服务器处理
history
模式的时候当vue-router的模式为history
模式的时候,将生成的文件部署到服务器的静态文件夹中,查看效果
初始的时候打开静态文件index.html,在里面点击进行切换进入到http://localhost:5050/home的路径中。随机点击刷新,会发现页面出现错误,无法正常显示。
原因是:当从index.html路径进入到http://localhost:5050/home路径的时候,没有发送任何网络请求,所以服务器不会处理,一旦重新刷新,那么就将该路径进行HTTP请求发送给服务器,而服务器中无相关的/home文件路径,所以就报错
服务器端只响应/api路径的请求
如何解决此问题,vue2官网中推荐使用 connect-history-api-fallback 中间件
。
npm i connect-history-api-fallback
安装该中间件const history = require('connect-history-api-fallback')
引入该中间件app.use(history())
使用该中间件提供的方法,一定需要在定义静态文件夹前使用