第六单元 vue路由
一、本单元教学目标
(Ⅰ)重点知识目标
<span style="background-color:#333333"><span style="color:#b8bfc6">1. 安装 vue-router
2. 使用 vue-router
3. 路由模式
4. 嵌套路由
5. 动态路由
6. 命名路由
7. 路由传参
8. 路由元信息
9. 路由守卫
10. keep-alive与路由</span></span>
二、本单元知识详讲
6.1 路由简介
6.1.1 什么是路由?
路由就是导航,路由的本质就是一种映射关系
根据不同的url请求,返回对应不同的资源。那么url地址和资源(html)之间就有一种对应的关系,这种对应的关系就是路由
a标签-->一种跳转(链接)方式-->路由的一部分。
6.1.2 路由发展的两个阶段。
1.后端路由阶段
-
多页面应用MPA
MPA 多页面应用(Multi Page Application)
定义:整个网站有多个HTML页面,页面跳转需要整个网页全部刷新。
示例:新闻_电脑爱好者
早期的网站开发,整个 HTML 页面都是是由服务器来渲染的,一个页面会有一个对应的 URL,客户端发起请求时,URL 会被发送到服务器,服务器进行各种处理后,直接生成渲染好对应的HTML 页面
, 返回给客户端进行展示, 这种操作, 就是后端路由来完成的。
-
后端路由的定义
一个url对应
一个完整的HTML页面
,完成这种对应的关系就需要后端路由来实现。
概念:根据不同的URL请求,返回不同的内容。
本质:
URL请求地址
与服务器资源
之间的对应关系
2.前端路由阶段
前提:随着 Ajax 的出现,有了前后端分离的开发模式:后端只提供 API 来返回数据,前端通过 Ajax 获取数据,并且可以通过 JS将数据渲染到页面中,这儿就产生了一个问题:怎么在只有一个HTML页面的情况下,展示更多的内容,于是就产生了一种开发应用的模式——单页面应用SPA。
什么是单页面富应用SPA?
单页面富应用,即单页Web应用(single page web application,SPA),就是只有一张 Web 页面(一个html)的应用,并在用户与应用程序交互时可以不刷新和跳转页面就动态更新该页面。
单页面应用的内容切换就需要用到前端路由来实现。
前端路由的定义
简单的说,就是在保证只有一个 HTML 页面的情况下,当用户交互时不刷新和不跳转页面的同时,为每个url匹配一个视图。
示例:卖座电影 只加载 中间 不同的内容部分
概念:监听URL地址的变化,显示不同的页面元素组件,页面不进行整体的刷新。(改变 url 时浏览器不会向服务器发送请求)
本质:
URL请求地址
与组件
之间的对应关系,
前端路由的工作方式
-
用户点击了页面上的路由链接
-
导致了URL地址栏中的Hash值发生了变化
-
前端路由监听了到Hash地址的变化
-
前端路由把当前Hash地址对应的组件渲染都浏览器中
6.1.3 前端路由原理和实现
路由分为两种模式,一种是hash模式,一种是history模式。
1. URL 的 hash
URL 的 hash 也就是锚点(#),本质上是改变 window.location 的 href 属性,可以通过直接赋值 location.hash 来改变 href,但是页面不发生刷新
hash模式有个明显的特征,就是url中有 # 号,例如 www.baidu.com/#query=123
,而 #query=123
就是我们期望的 hash 值。
<span style="background-color:#333333"><span style="color:#b8bfc6"><span style="color:#da924a">// 设置hash值</span>
<span style="color:#b8bfc6">window</span>.<span style="color:#b8bfc6">location</span>.<span style="color:#b8bfc6">hash</span> <span style="color:#b8bfc6">=</span> <span style="color:#d26b6b">'xxx'</span>
<span style="color:#da924a">//获取hash值</span>
<span style="color:#c88fd0">let</span> <span style="color:#8d8df0">hash</span> <span style="color:#b8bfc6">=</span> <span style="color:#b8bfc6">window</span>.<span style="color:#b8bfc6">location</span>.<span style="color:#b8bfc6">hash</span>
<span style="color:#da924a">//监听hash值变化,点击浏览器前进后退也会触发</span>
<span style="color:#b8bfc6">window</span>.<span style="color:#b8bfc6">addEventListener</span>(<span style="color:#d26b6b">'hashchange'</span>, (<span style="color:#8d8df0">event</span>)<span style="color:#b8bfc6">=></span>{
<span style="color:#c88fd0">let</span> <span style="color:#8d8df0">newURL</span> <span style="color:#b8bfc6">=</span> <span style="color:#9fbad5">event</span>.<span style="color:#b8bfc6">newURL</span>; <span style="color:#da924a">// hash 改变后的新 url</span>
<span style="color:#c88fd0">let</span> <span style="color:#8d8df0">oldURL</span> <span style="color:#b8bfc6">=</span> <span style="color:#9fbad5">event</span>.<span style="color:#b8bfc6">oldURL</span>; <span style="color:#da924a">// hash 改变event前的旧 url</span>
<span style="color:#b8bfc6">console</span>.<span style="color:#b8bfc6">log</span>(<span style="color:#9fbad5">newURL</span>,<span style="color:#9fbad5">oldURL</span>)
},<span style="color:#84b6cb">false</span>)</span></span>
<span style="background-color:#333333"><span style="color:#b8bfc6"><span style="color:#999977"><</span><span style="color:#7df46a">style</span><span style="color:#999977">></span>
<span style="color:#b7b3b3">.hide</span>{
<span style="color:#b8bfc6">display</span>: <span style="color:#84b6cb">none</span>;
}
<span style="color:#b7b3b3">.show</span>{
<span style="color:#b8bfc6">display</span>: <span style="color:#84b6cb">block</span>;
}
<span style="color:#999977"></</span><span style="color:#7df46a">style</span><span style="color:#999977">></span>
<span style="color:#999977"><</span><span style="color:#7df46a">body</span><span style="color:#999977">></span>
<span style="color:#999977"><</span><span style="color:#7df46a">a</span> <span style="color:#7575e4">href</span>=<span style="color:#d26b6b">"#index"</span><span style="color:#999977">></span>首页<span style="color:#999977"></</span><span style="color:#7df46a">a</span><span style="color:#999977">></span>
<span style="color:#999977"><</span><span style="color:#7df46a">a</span> <span style="color:#7575e4">href</span>=<span style="color:#d26b6b">"#news"</span><span style="color:#999977">></span>新闻<span style="color:#999977"></</span><span style="color:#7df46a">a</span><span style="color:#999977">></span>
<span style="color:#999977"><</span><span style="color:#7df46a">div</span> <span style="color:#7575e4">class</span>=<span style="color:#d26b6b">"content-home show"</span><span style="color:#999977">></span>首页-home<span style="color:#999977"></</span><span style="color:#7df46a">div</span><span style="color:#999977">></span>
<span style="color:#999977"><</span><span style="color:#7df46a">div</span> <span style="color:#7575e4">class</span>=<span style="color:#d26b6b">"content-news hide"</span><span style="color:#999977">></span>新闻-content<span style="color:#999977"></</span><span style="color:#7df46a">div</span><span style="color:#999977">></span>
<span style="color:#999977"><</span><span style="color:#7df46a">script</span><span style="color:#999977">></span>
<span style="color:#c88fd0">var</span> <span style="color:#8d8df0">contentHome</span><span style="color:#b8bfc6">=</span> <span style="color:#b8bfc6">document</span>.<span style="color:#b8bfc6">querySelector</span>(<span style="color:#d26b6b">'.content-home'</span>)
<span style="color:#c88fd0">var</span> <span style="color:#8d8df0">contentNews</span><span style="color:#b8bfc6">=</span> <span style="color:#b8bfc6">document</span>.<span style="color:#b8bfc6">querySelector</span>(<span style="color:#d26b6b">'.content-news'</span>)
<span style="color:#da924a">// 路由对象:对应关系; url路径->组件</span>
<span style="color:#c88fd0">var</span> <span style="color:#8d8df0">routers</span> <span style="color:#b8bfc6">=</span> [
{
<span style="color:#b8bfc6">path</span>: <span style="color:#d26b6b">'#index'</span>,
<span style="color:#b8bfc6">component</span>: <span style="color:#c88fd0">function</span> () {
<span style="color:#b8bfc6">console</span>.<span style="color:#b8bfc6">log</span>(<span style="color:#d26b6b">'渲染的首页组件内容、、'</span>)
<span style="color:#b8bfc6">contentHome</span>.<span style="color:#b8bfc6">className</span><span style="color:#b8bfc6">=</span><span style="color:#d26b6b">'content-home show'</span>
<span style="color:#b8bfc6">contentNews</span>.<span style="color:#b8bfc6">className</span><span style="color:#b8bfc6">=</span><span style="color:#d26b6b">'content-home hide'</span>
}
},
{
<span style="color:#b8bfc6">path</span>: <span style="color:#d26b6b">'#news'</span>,
<span style="color:#b8bfc6">component</span>: <span style="color:#c88fd0">function</span> () {
<span style="color:#b8bfc6">console</span>.<span style="color:#b8bfc6">log</span>(<span style="color:#d26b6b">'渲染的新闻的组件内容、、'</span>)
<span style="color:#b8bfc6">contentHome</span>.<span style="color:#b8bfc6">className</span><span style="color:#b8bfc6">=</span><span style="color:#d26b6b">'content-home hide'</span>
<span style="color:#b8bfc6">contentNews</span>.<span style="color:#b8bfc6">className</span><span style="color:#b8bfc6">=</span><span style="color:#d26b6b">'content-home show'</span>
}
}
]
<span style="color:#c88fd0">function</span> <span style="color:#8d8df0">callback</span>(<span style="color:#8d8df0">event</span>) {
<span style="color:#b8bfc6">console</span>.<span style="color:#b8bfc6">log</span>(<span style="color:#9fbad5">event</span>);<span style="color:#da924a">//event 事件对象</span>
<span style="color:#b8bfc6">console</span>.<span style="color:#b8bfc6">log</span>(<span style="color:#b8bfc6">location</span>);<span style="color:#da924a">//loction</span>
<span style="color:#c88fd0">for</span> (<span style="color:#c88fd0">var</span> <span style="color:#8d8df0">r</span> <span style="color:#c88fd0">in</span> <span style="color:#b8bfc6">routers</span>) {
<span style="color:#c88fd0">if</span> (<span style="color:#b8bfc6">routers</span>[<span style="color:#9fbad5">r</span>].<span style="color:#b8bfc6">path</span> <span style="color:#b8bfc6">==</span> <span style="color:#b8bfc6">location</span>.<span style="color:#b8bfc6">hash</span>) {<span style="color:#da924a">// 匹配哈希值 执行对应方法</span>
<span style="color:#b8bfc6">routers</span>[<span style="color:#9fbad5">r</span>].<span style="color:#b8bfc6">component</span>()
}
}
}
<span style="color:#da924a">// 监听浏览器hash改变事件 </span>
<span style="color:#b8bfc6">window</span>.<span style="color:#b8bfc6">addEventListener</span>(<span style="color:#d26b6b">'hashchange'</span>, <span style="color:#b8bfc6">callback</span>)
<span style="color:#999977"></</span><span style="color:#7df46a">script</span><span style="color:#999977">></span>
<span style="color:#999977"></</span><span style="color:#7df46a">body</span><span style="color:#999977">></span></span></span>
2.HTML5 的 history 模式
History对象主要有两个属性。
-
History.length (当前窗口访问过的网址数量,包括当前网页)
-
History.state (History堆栈最上层的状态值)
<span style="background-color:#333333"><span style="color:#b8bfc6">history.length;
history.state;</span></span>
方法
-
History.back():移动到上一个网址,等同于点击浏览器后退键。对于第一个访问的网址,该方法无效。
-
History.forward(): 移动到下一个网站, 等同于浏览器的后退键。 对于最后一个访问的网址,该方法无效。
-
History.go(): 接受一个整数作为参数, 以当前网址为基准, 默认参数为0, 相当于刷新页面。
<span style="background-color:#333333"><span style="color:#b8bfc6">history.back();
history.forward();
history.go(1); // 相当于history.forward();
histroy.go(-1); //相当于history.back();
history.go(0); //刷新当前页</span></span>
注意: 移动到以前访问过的页面时, 页面通常是从浏览器缓中加载, 而不是重新要求服务器发送新的网页。
History.pushState()
该方法用于在历史中添加一条记录。 pushState()方法不会触发页面刷新, 只是导致History对象发生变化, 地址栏会有变化。
语法: history.push(object, title, url);
-
object: 通过pushState方法可以将该对象传递到新的页面中。 不需要可以填null。
-
title: 标题, string,目前几乎没有浏览器支持该参数,可以填空字符串。
-
url: 新的网址, 必须与当前页面处在同一个域。 如果是跨域网址则会报错(这么设计是为了防止恶意代码让用户以为他们是在另一个网站上)。
<span style="background-color:#333333"><span style="color:#b8bfc6">var data = {name: 'data'};
history.pushState(data,' ','/open');
console.log(history.state);// {name: 'data'}
</span></span>
注意: 如果pushState的url参数设置了一个新的hash, 并不会触发hashchange事件。反过来, 如果url的hash变动了,则会在History对象创建一条浏览记录。
History.replaceState() 该方法用来修改History对象的当前记录, 用法与pushState()方法一样
<span style="background-color:#333333"><span style="color:#b8bfc6">history.pushState({a: 1}, ' ', '?a=1');
//url为 https://www.baidu.com?a=1
history.pushState({a: 2}, ' ', '?a=2');
//url为 https://www.baidu.com?a=2
history.pushState({a: 3}, ' ', '?a=3');
//url为 https://www.baidu.com?a=3
history.back();
//url为 https://www.baidu.com?a=1
history.back();
//url为 https://www.baidu.com
history.go(2);
//url为 https://www.baidu.com?a=3</span></span>
popstate事件 每当处于激活状态的历史记录条目发生变化时, 就会触发popstate事件。
-
仅仅调用pushState()或replaceState()方法, 并不会触发popstate事件。
-
只有点击浏览器前进倒退按钮,或者使用js调用history.back(),history.forward(),history.go()方法时才会触发。
-
popstate事件只针对同一个文档, 如果浏览历史的切换, 导致加载不同的文档, 该事件也不会触发。
-
页面第一次加载的时候, 浏览器不会触发popstate事件。
<span style="background-color:#333333"><span style="color:#b8bfc6">window.addEventListener('popstate',function(e) {
var s1 = e.state;
var s2 = history.state;
// s1 == s2
});</span></span>
注意: history致命的缺点是当改变页面地址后,强制刷新浏览器时, 如果后端没有对应的地址,会404,因为刷新是拿当前地址去请求服务器, 如果服务器没有相应的响应,页面则会404。
6.2 路由的基本使用
vue-router4 是 Vue.js 官方的路由插件,它和 vue.js 是深度集成的,适合用于构建单页面应用。
vue-router 是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来,在 vue-router 的单页面应用中, 页面路径的改变就是组件的切换。
6.2.1 安装 vue-router
<span style="background-color:#333333"><span style="color:#b8bfc6"># npm
npm install vue-router@4
# vue-cli add 插件形式
vue add router </span></span>
6.2.2 使用 vue-router
<span style="background-color:#333333"><span style="color:#b8bfc6">1. 创建路由组件
2. 定义路由配置对象routes
3. 创建路由实例createRouter,并且传入路由routes映射配置
4. 在 Vue 实例中(main.js)导入路由对象,并且调用 Vue.use(VueRouter)
5. 添加路由组件渲染容器 `<router-view>` (占坑)</span></span>
1. router-link
和router-view
:
-
<router-link>
: 该标签是一个 vue-router 中已经内置的组件, 它默认会被渲染成一个<a>
标签 -
<router-view>
: 路由出口,该标签会根据当前的url路径,动态渲染出不同的组件
2. 创建路由组件
3. 创建路由实例
配置路由映射: 组件和路径映射关系
4. 挂载到Vue实例中(main.js)
最终效果如下:
6.2.3 router-link其他属性
1. to 属性
在前面的 <router-link>
中,我们只是使用了一个属性 to ,用于指定跳转的路径
2.replace 属性
-
replace 不会留下 history 记录,所以指定 replace 的情况下, 后退键返回不能返回到上一个页面中
<span style="background-color:#333333"><span style="color:#b8bfc6"><router-link to="/home" tag="button" replace>首页</router-link>
</span></span>
3. active-class:
当<router-link>
对应的路由匹配成功时,会自动给当前元素设置一个 router-link-active
的class
设置 active-class
可以修改默认的名称:
<span style="background-color:#333333"><span style="color:#b8bfc6"><router-link to="/home" tag="button" replace active-class="active">首页</router-link></span></span>
在进行高亮显示的导航菜单或者底部 tabbar 时,会使用到该类,比如想设置按钮点击时变为红色:
也可以通过 router 实例的属性linkActiveClass进行修改:(了解即可)
但是通常不会修改类的属性, 会直接使用默认的 router-link-active 即可。
6.2.4 路由的默认路径 redirect
默认情况下, 进入网站的首页,我们希望 <router-view>
渲染首页的内容,但是在上面的实现中,默认没有显示首页组件,必须让用户点击才可以
如何可以让路径默认跳到到首页,并且<router-view>
渲染首页组件呢?
可以在 routes 中又配置了一个映射:
-
path:根路径
/
-
redirect:重定向,也就是将根路径重定向到
/home
的路径下
这样,打开页面时,就会默认显示首页的内容了
补充点 有时候路径找不到我们也会在最后设置一个路由重定向。
<span style="background-color:#333333"><span style="color:#b8bfc6">{
path: '/:pathMatch(.*)',
redirect: '/error'
}</span></span>
6.2.5 编程式导航
有时候,页面的跳转可能需要执行对应的 JavaScript 代码,这个时候,就可以使用编程式导航的路由跳转方式了,我们将代码修改如下:
<span style="background-color:#333333"><span style="color:#b8bfc6">// 跳转到上一次浏览的页面
this.$router.go(-1)
// 跳转到指定路径
this.$router.replace('/menu')
// 按路由名字跳转
this.$router.replace({name:'menuLinkRouter'})
// 最常用 压栈方式 push
this.$router.push('/menu')
// 命名的路由
this.$router.push({ name: 'menu', params: { orderId: '123' }})
// 带查询参数,变成 /menu?plan=private
this.$router.push({ path: 'menu', query: { plan: 'private' }})</span></span>
6.3 Vue-router 嵌套路由
嵌套路由是一个很常见的功能 ,比如在 home 页面中,我们希望通过 /home/news
和 /home/message
访问一些内容,一个路径映射一个组件,访问这两个路径也会分别渲染两个组件。
路径和组件的关系如下:
实现嵌套路由的3个步骤:
-
在父组件内部显示子组件
-
创建对应的子组件,并且在路由映射中配置对应的子路由
-
定义两个子组件
-
配置子组件的路由 并设置默认路径
-
查看显示效果
6.4 动态路由匹配
在某些情况下,一个页面的 path 路径可能是不确定的,比如我们进入用户界面时,它应该对所有用户进行渲染,但用户 ID 不同,希望是如下的路径:
/user/aaaa
或 /user/bbbb
,除了有前面的 /user
之外,后面还跟上了用户的 ID。
这种 path 和 Component 的匹配关系,称之为动态路由(也是路由传递数据的一种方式)
-
通过 v-bind:to 在组件中手动绑定一个用户 ID:
-
在
vue-router
的路由路径中使用“动态路径参数”(dynamic segment) :
-
更新
User
的模板,输出当前用户的 ID:
-
查看显示效果:
6.5 路由传参
传递参数主要有两种类型: params 和 query
6.5.1 query传参
-
形成的路径:
/router?id=123
,/router?id=abc
-
路由配置文件中(route.js)的格式:
{path: '/router',component: Detail}
-
传递的方式:
<span style="background-color:#333333"><span style="color:#b8bfc6"><!--字符串形式-->
<router-link to="/detail/1?title=我的歌单">我的歌单</router-link>
<!-- 对象形式-->
<router-link :to="{path:'/detail/2',query:{title:'最近播放'}}">最近播放</router-link></span></span>
-
组件内访问:
this.$route.query.title
6.5.2 params传参
-
形成的路径:
/router/123
,/router/abc
-
路由配置文件中(route.js)的格式:
{ path: "/detail/:id",component: Detail}
-
传递的方式:
<span style="background-color:#333333"><span style="color:#b8bfc6"><!--字符串形式,在 path 后面跟上对应的值 格式: 路由地址/参数1/参数2 -->
<router-link tag = "li" to="/detail/1">我的歌单</router-link>
<!--对象形式-->
<router-link tag = "li" :to="{name:'detail',params:{id:1}}">我的歌单</router-link>
<!--在传递params参数并目使用对象的写法时,不再是path属性了,而是name配置项,而且必须是name,如果是path配置项,会报错-->
// router.js
{ path: "/detail/:id", component: Detail, name: "detail"}</span></span>
-
组件内访问:
this.$route.params.id
6.5.3 访问参数
-
组件中通过选项式api 获取route对象
获取参数是通过 route 对象获取的,路由对象会被注入每个组件中,赋值为 this.$route
,并且当路由切换时,路由对象会被更新。
<span style="background-color:#333333"><span style="color:#b8bfc6">console.log(this.$route)</span></span>
-
组件中通过组合式api
<span style="background-color:#333333"><span style="color:#b8bfc6">import {useRoute} from 'vue-router'
const router = useRouter();</span></span>
-
js文件中获取路由:导入路由实例router对象
<span style="background-color:#333333"><span style="color:#b8bfc6">// util.js 例如其他工具函数的js文件中 需要依赖路由对象
import router from "@/router";
router.push({ name: pageName })</span></span>
6.5.4 prop将路由参数与组件解耦
在组件中接收路由参数需要this.$route.params.id
,代码冗余,可以在路由表里配置props:true。
<span style="background-color:#333333"><span style="color:#b8bfc6">{ path:"/detail/:id",component:()=>import("../views/Detail.vue"),name:"detail",props:true}</span></span>
在路由组件中可以通过props接收id参数去使用
<span style="background-color:#333333"><span style="color:#b8bfc6">const User = {
// 请确保添加一个与路由参数完全相同的 prop 名
props: ['id'],
template: '<div>User {{ id }}</div>'
}</span></span>
在页面中就可以通过 {{id}} 的方式来使用路由传递的参数
6.5.5 route 和 router 的区别
-
router 为 VueRouter 路由实例,想要导航到不同 URL,则使用
router.push router.replace
方法 -
$route 为当前 router 跳转对象,里面可以获取路由参数 name、path、query、params 等
6.6 命名路由
我们可以给路由对象配置name属性,这样的话,我们在跳转的时候直接写"name:名称"就会快速的找到此name属性对应的路由,不需要写大量的url path路径了。
6.6.1 案例1: 基本使用
<span style="background-color:#333333"><span style="color:#b8bfc6">const router=new VueRouter({
routes: [
{path:'/',name:'homeLinkRouter',component:Home},
]
})</span></span>
2.页面使用 用 v-bind 的形式
<span style="background-color:#333333"><span style="color:#b8bfc6"><li class="nav-item active">
<router-link :to="{name:'homeLinkRouter'}" class="nav-link">首页</router-link>
</li></span></span>
6.6.2 案例2:动态路由
在List.vue的data中将path改为name属性:
<span style="background-color:#333333"><span style="color:#b8bfc6">data(){
return{
navs:[
/* {id:1,title:"音频",path:"/list/audio"},
{id:2,title:"视频",path:"/list/video"} */
{id:1,title:"音频",name:"audio"},
{id:2,title:"视频",name:"video"}
]
}
}</span></span>
将<router-link>
中的to属性改为"name:名称"形式
<span style="background-color:#333333"><span style="color:#b8bfc6"><router-link
v-for = "nav in navs"
:key = "nav.id"
:to = "{name:nav.name}"
active-class = "title"
>
{{nav.title}}
</router-link></span></span>
给detail也添加一个name属性
<span style="background-color:#333333"><span style="color:#b8bfc6">{ path:"/detail/:id",component:()=>import("../views/Detail.vue"),name:"detail"}</span></span>
Home.vue中的<router-link>
可以改写成如下格式:
<span style="background-color:#333333"><span style="color:#b8bfc6"><router-link tag = "li" :to = "{name:'detail',params:{id:'1'},query:{title:'最近播放'}}">我的歌单</router-link></span></span>
query传递的参数通过this.$route.query.title来获取
params传递的参数通过this.$route.parmas.id来获取 ,通过对象传递params时 必须配合name
6.7 路由的懒加载 (异步加载)
6.7.1 认识路由懒加载
因为vue首次加载的时候把页面暂时不需要展示的组件也一次加载了,当打包构建应用时,Javascript 包会变得非常大,影响页面加载,如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
为了实现这种效果,我们可以使用路由的懒加载。
路由懒加载的主要作用就是将路由对应的组件打包成一个个的 js 代码块,只有在这个路由被访问到的时候,才加载对应的组件。
6.7.2 懒加载的三种方式
-
结合 Vue 的异步组件和 Webpack 的代码分析
<span style="background-color:#333333"><span style="color:#b8bfc6">const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};</span></span>
-
AMD 写法
<span style="background-color:#333333"><span style="color:#b8bfc6">const About = resolve => require(['../components/About.vue'], resolve);</span></span>
-
推荐ES6写法
在ES6中,可以用更加简单的写法来组织 Vue 异步组件和 Webpack 的代码分割
<span style="background-color:#333333"><span style="color:#b8bfc6">const Home = () => import('../components/Home.vue')</span></span>
6.7.3 路由懒加载的效果
1.开发环境下观察控制台:
-
默认导入组件方式:head中.vue组件的style 全部加载了。
-
路由懒加载导入方式:只有当前路由对应的组件 的 style 被加载
2.打包后看dist
6.8 路由元信息
路由元信息,其实就是当前路由的额外的信息。元信息的主要作用是在路由守卫以及在路由渲染视图的时候。(路由守卫和路由渲染视图在一节)
-
权限校验标识
-
标题名称
-
路由组件的过渡名称
-
路由组件持久化缓存(keep-alive)的相关配置
<span style="background-color:#333333"><span style="color:#b8bfc6">import { createRouter, createWebHashHistory } from "vue-router"
// const Home=()=>import("../views/home.vue")
const routers = [
{
path: "/home",
component: () => import("../views/home.vue"),
// 配置路由元信息 路由的额外信息
meta: {
title: "home", // 可以定义网页的标题
requiresAuth: true// 只有经过身份验证的用户才能创建帖子
}
}
]
// 要被导出的变量
const router = createRouter({
routes: routers,
history: createWebHashHistory()
})
export default router</span></span>
我们可以在导航守卫或者是路由对象中访问路由的元信息数据。
一个路由匹配到的所有路由记录会暴露为 $route
对象(还有在导航守卫中的路由对象)的$route.matched
数组。我们需要遍历这个数组来检查路由记录中的 meta
字段,但是 Vue Router 还为你提供了一个 $route.meta
方法,它是一个非递归合并所有 meta
字段的(从父字段到子字段)的方法。这意味着你可以简单地写
<span style="background-color:#333333"><span style="color:#b8bfc6">router.beforeEach((to, from) => {
//通过to.meta来获取路由元信息
console.log(to.meta.requiresAuth);
// 通过登录后存在store中的登录状态检查, 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})</span></span>
6.9 Vue-router 导航守卫
就好比公主的护卫一样,一旦有人想要靠近公主,那么首先就会被护卫拦下来,通过验证/得到允许后才可以靠近。
路由也是一样的。我们在使用其他网站时,在没有登录或者其他情况下,如果想要访问某些信息,往往是需要跳转到登录页,在登录之后才可以进行访问。这其中就是路由守卫在起作用。
6.9.1 思考:如何改变每个页面的标题
应用场景:在一个 SPA 应用中,如何改变网页的标题呢?
普通的修改方式:在每一个路由对应的组件 .vue
文件中,通过 mounted 声明周期函数,执行对应的代码进行修改。
但是当页面比较多时,需要在多个页面执行类似的代码,所以这种方式不容易维护。
更好的办法是使用导航守卫。注意:在router.js中挂载之外的组件,是没有钩子函数的。但是如果你使用了并不会报错,只是没反应。
1、确保该组件挂载在router.js 里面。 2、没挂载在router.js 里面的组件如果需要监听路由的话,用watch 监听 $ route 就行了。
<span style="background-color:#333333"><span style="color:#b8bfc6">watch:{
$route: function(to, from) {}
}</span></span>
6.9.2 导航守卫的作用
导航守卫主要用来监听路由的进入和离开,vue-router 提供了 beforeEach 和 afterEach 等钩子函数,它们会在路由即将改变前 及 改变后触发,以此来监听路由的变化。
6.9.3 路由守卫的分类
路由守卫分为三类:全局守卫、路由独享守卫、组件内守卫。(门卫) 其中,全局守卫分为:全局前置守卫和全局后置守卫。
全局守卫的书写位置是在router文件夹下的index.js文件中。
6.9.4 全局前置守卫 beforeEach
全局前置守卫,顾名思义:纵观全局,只要有路由的切换,就会进行守卫校验:
<span style="background-color:#333333"><span style="color:#b8bfc6">// router/index.js
...
// 全局前置守卫
router.beforeEach((to, from, next) => {
})
...</span></span>
主要目的是 全局拦截 路由跳转
<span style="background-color:#333333"><span style="color:#b8bfc6">// router/index.js
import { createRouter, createWebHashHistory } from "vue-router"
import Home from '../views/home.vue'
const routers = [
{
path: "/home",
component: Home,
}
]
const router = createRouter({
routes: routers,
history: createWebHashHistory()
})
// 全局前置守卫
// 导航卫士
router.beforeEach((to, from, next) => {
console.log(to, 555)
if (to.path == '/login' || to.path == '/register') {
next()
} else {
alert('请登录')
next({ name: 'loginLinkRouter' })
}
})
export default router</span></span>
案例——修改标题
1、在钩子当中利用 meta 元信息 来定义标题
2、利用导航守卫,修改标题
查看修改效果
3、判断登录状态,通过路径 和 token来判断,如果不是login页面 且没有token 自动跳转到token
<span style="background-color:#333333"><span style="color:#b8bfc6">router.beforeEach((to,from,next)=>{
console.log(to,from,777);//
document.title=to.matched[0].meta.title
if(to.path!='/login' && !sessionStorage.token ){
next('/login')
}
next()
// next(false)//终止
// next('/')//跳转到具体的路径 判断用户是否登录
// next({path:'/'})//跳转到具体的路径
//你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop或 router.push 中的选项。
})</span></span>
登录拦截及用户权限访问控制问题
一个很常见的需求就是对未登录的用户进行路由拦截和用户的权限访问, 这时候我们需要在router的里面添加meta字段进行自定义一些信息。像下面这样:
然后再main.js 加入全局的路由拦截。
<span style="background-color:#333333"><span style="color:#b8bfc6">router.beforeEach((to, from, next) =>{
if(to.meta.requireAuth){ //是否需要登录拦截
if(store.state.tokens.token){ //已登录
next()
}else{
next({// 验证不通过 不能放行
path: '/',
query: {redirect : to.fullPath}
})
}
}else{// 不需要登录 直接放行
next()
}
})</span></span>
同理,用户权限的认证也可以这么做。另外需要注意的是,这个登录状态需要使用localstorage或者cookie保存,只存在store里面会导致页面一刷新登录状态就没了(当然你可以在页面mounted的时候去后台获取状态,然后改变store)。
全局前置守卫的使用场景?
-
初识化的时候被调用;
-
每一次路由切换之前被调用:
前置守卫的三个参数 to from next
-
to: 表示
要到哪里去
,存储着即将要进入的目的地的相关路由信息; -
from: 表示
从哪里来
, 存储着来源的相关信息 -
next: 表示
放行
, 如果传入路由地址,则表示跳转到指定路由;调用该方法来 resolve 这个钩子后,才能进入下一个钩子
6.9.5 全局后置守卫 afterEach
<span style="background-color:#333333"><span style="color:#b8bfc6">router.afterEach((to,from)=>{})</span></span>
后置守卫的两个参数
-
to:进入到哪个路由去,
-
from:从哪个路由来。
-
没有 next() 函数
-
包括后续的路由独享守卫和组件内守卫。
后置守卫只接收两个参数的原因是:因为他的使用场景是在路由切换后才执行的,此时有没有next已经没 有任何意义了。
使用场景
-
初始化的时候被调用
-
每次路由切换之后被调用
-
在路由切换成功之后修改网页页签标题
-
做 loadingBar
afterEach 没有this
<span style="background-color:#333333"><span style="color:#b8bfc6">//想通过router来访问store
router.app.$options.store.state.showLoginState=false</span></span>
6.9.6 路由独享的守卫beforeEnter
顾名思义,路由独享守卫就是某一个路由的专享,其他路由使用不了,用法与全局守卫一致,只是,将其写进其中一个路由对象中,只在这个路由下起作用。同样是接收to、from、next三个参数:
<span style="background-color:#333333"><span style="color:#b8bfc6">const routes=[
{ path:"/",redirect:'/home',meta: { title: '网抑云音乐' } },
{ path:"/home",component:Home,meta: { title: '网抑云音乐' } },
{ path:"/about",component:About,meta: { title: '关于' } },
{ path:"/top/playlist",component:PlayList,meta: { title: '列表' } },
{ path:"/detail",component:About,meta: { title: '详情' },
beforeEnter: (to, from, next) => {
alert('非登录状态下不能访问此页面')
next('/login')
}
},
]</span></span>
路由独享守卫没有后置守卫
6.9.7 三个组件内守卫
写在组件内的路由守卫,共有三个配置项
-
beforeRouteEnter
-
beforeRouteUpdate
-
beforeRouteLeave
beforeRouteEnter
顾名思义:到达这个组件时
<span style="background-color:#333333"><span style="color:#b8bfc6">beforeRouteEnter:(to,from,next)=>{})</span></span>
应用场景:登录后 在首页显示提示信息,但是 只能从login页面进入 才提示
<span style="background-color:#333333"><span style="color:#b8bfc6">export default {
name: 'Home',
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建,不过你可以通过传一个回调给 next来访问组件实例
if(from.path=='/login'){
next(vm=>{//通过vm来访问this实例
vm.$message({
message: '登录成功',
type: 'success'
});
})
}
next()
}
}</span></span>
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteUpdate
当一个组件被重复调用的时候执行该守卫(2.2 新增)
在Deatil.vue中添加beforeRouteUpdate:
<span style="background-color:#333333"><span style="color:#b8bfc6">beforeRouteUpdate(to,from,next){
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
console.log("beforeRouteUpdate")
next()
}</span></span>
beforeRouteLeave
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
<span style="background-color:#333333"><span style="color:#b8bfc6">beforeRouteLeave(to, from, next) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}</span></span>
6.10 命名视图
App.vue
<span style="background-color:#333333"><span style="color:#b8bfc6"><div class="container">
<Header></Header>
</div>
<div class="container">
<router-view></router-view>
</div>
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-4">
<router-view name='orderingGuide'></router-view>
</div>
<div class="col-sm-12 col-md-4">
<router-view name='delivery'></router-view>
</div>
<div class="col-sm-12 col-md-4">
<router-view name='history'></router-view>
</div>
</div>
</div>
</div></span></span>
router.js
<span style="background-color:#333333"><span style="color:#b8bfc6">{
path:'/',
name:'homeLinkRouter',
components:{
'default':Home,
"orderingGuide":OderingGuide,
"delivery":Delivery,
"history":History,
}
},</span></span>
6.11 动态路由
我们一般使用动态路由都是后台会返回一个路由表前端通过调接口拿到后处理 (后端处理路由),主要使用的方法就是 router.addRoute
6.11.1 添加路由 addRoute
动态路由主要通过两个函数实现。router.addRoute() 和 router.removeRoute()。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由
<span style="background-color:#333333"><span style="color:#b8bfc6">router.addRoute({ path: '/about', component: About })</span></span>
6.11.2 删除路由 removeRoute
有几个不同的方法来删除现有的路由:
<span style="background-color:#333333"><span style="color:#b8bfc6">// 1. 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
router.addRoute({ path: '/about', name: 'about', component: About })
// 这一行将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
router.addRoute({ path: '/other', name: 'about', component: Other })
// 2.通过调用 router.addRoute() 返回的回调:
const removeRoute = router.addRoute(routeRecord)
removeRoute() // 删除路由如果存在的话
// 3.当路由没有名称时,这很有用。
// 通过使用 router.removeRoute() 按名称删除路由:
router.removeRoute('about')</span></span>
当路由被删除时,所有的别名和子路由也会被同时删除
6.11.3 查看现有路由
Vue Router 提供了两个功能来查看现有的路由:
-
router.hasRoute():检查路由是否存在。
-
router.getRoutes():获取一个包含所有路由记录的数组。
前端代码
<span style="background-color:#333333"><span style="color:#b8bfc6">// 1. 导入路由对象.
import { createRouter, createWebHashHistory,createWebHistory } from 'vue-router'
// 2. 定义一些路由 每个路由都需要映射到一个组件。
const routes = [
{ path: '/', redirect: '/home' },
]
const router = createRouter({
history: createWebHistory(),
routes, // `routes: routes` 的缩写
})
const initRouter = async () => {
// const result = await axios.get('http://localhost:9999/login', { params: formInline });//get是params参数,post没有限制。formInline是表单的信息,通过.user.password之类的能获取到账号密码。
// 假数据
const result = {
route: [
{path: "/home", name: "Home", component: "Home.vue"},
{path: "/about", name: "About", component: "About.vue"},
{path: "/user", name: "User", component: "User.vue"}
]
}
result.route.forEach((v) => {//后端返回的信息进行动态添加
console.log(v.component);
router.addRoute({
path: v.path,
name: v.name,
//这儿不能使用@
component: () => import(`./../views/${v.component}`)//最关键的组件需要我们动态的去拼接,这里使用模板字符串
//然后就是import()动态引入
})
router.push('/home')//跳转页面,验证通过,进入登录页面
})
console.log(router.getRoutes());
}
initRouter()
export default router</span></span>
后端代码
<span style="background-color:#333333"><span style="color:#b8bfc6">import express from 'express'
const app = express()
app.get('/login', (req, res) => {//这里使用get请求所以必须使用query参数
res.header("Access-Control-Allow-Origin", "*");
if (req.query.user == 'admin' && req.query.password == '123456') {//根据前端传过来的信息返回不同的信息回去
res.json({
route: [
{path: "/home", name: "Home", component: "Home.vue"},
{path: "/about", name: "About", component: "About.vue"},
{path: "/user", name: "User", component: "User.vue"}
]
})
} else {
res.json({
code: 400,
mesage: "账号密码错误"
})
}
})
app.listen(9999, () => {
console.log('http://localhost:9999');
})</span></span>
6.12 keep-alive与路由
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。它是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
6.12.1 vue2写法
<span style="background-color:#333333"><span style="color:#b8bfc6"><keep-alive>
<router-view>
<!-- 所有路径匹配到的试图组件都会被缓存! -->
</router-view>
</keep-alive></span></span>
6.12.2 vue3写法
transition
和 keep-alive
现在必须通过 v-slot
API 在 RouterView
内部使用:
<span style="background-color:#333333"><span style="color:#b8bfc6"><router-view v-slot="{ Component }">
<transition>
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view></span></span>
你经常会在手机上有这样的体验,比如你在商品列表页,找了半天找到一个商品,然后进入了商品详情页面,结果发现不是,当你在返回商品列表页面的时候,还是原来你滚动的位置,这样的业务场景其实就是用到了,keep-alive。
keep-alive 还有两个非常重要的属性:
-
include - 字符串或正则表达,只有匹配的组件会被缓存
-
exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
让部分组件不缓存:
<span style="background-color:#333333"><span style="color:#b8bfc6"><keep-alive exclude="Profile,User"><router-view/></keep-alive></span></span>
6.12.3 keep-alive的2个生命周期钩子
当一个组件实例从 DOM 上移除但因为被 <KeepAlive>
缓存而仍作为组件树的一部分时,它将变为不活跃状态而不是被卸载。当一个组件实例作为缓存树的一部分插入到 DOM 中时,它将重新被激活。
作用:缓存实例的生命周期,所独有的两个钩子,用于捕获路由组件的激活状态。
在使用 keep-alive
后,对应的生命周期可以用 activated
deactivated
, 来实现 监听
<span style="background-color:#333333"><span style="color:#b8bfc6"><script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'home',
activated() {
// 在首次挂载、
// 以及每次从缓存中被重新插入的时候调用(页面被激活)
console.log('页面被激活');
},
deactivated() {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
console.log('页面被卸载');
}
})
</script></span></span>
-
activated:keep-alive 组件激活时调用。
-
deactivated: keep-alive 组件停用时调用。
-
这两个钩子不仅适用于
<KeepAlive>
缓存的根组件,也适用于缓存树中的后代组件。
6.12.4 最大缓存实例数
我们可以通过传入 max
prop 来限制可被缓存的最大组件实例数。<KeepAlive>
的行为在指定了 max
后类似一个 LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。
<span style="background-color:#333333"><span style="color:#b8bfc6"><KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive></span></span>
6.13 路由补充(问题处理)
6.13.1 路由跳转报错的问题
<span style="background-color:#333333"><span style="color:#b8bfc6">//先存数据 在跳转 不然要报错Uncaught (in promise) undefined
//https://segmentfault.com/q/1010000020641790/先存储数据 再跳转path哦 你肯定是写反了 之所以你点击两下才能进去 是因为第一下没有跳转而是把数据存储了一下 第二次才是根据token实现瞄点跳转哦
sessionStorage.token=res.data.data.token
this.$router.push('/home')</span></span>
6.13.2 路由点2次报错Uncaught (in promise)
<span style="background-color:#333333"><span style="color:#b8bfc6">Avoided redundant navigation to current location: "/home".
避免对当前位置的冗余导航:“/home”。</span></span>
vue-router
在3.1.0
版本之后,push
和replace
方法会返回一个promise对象,如果跳转到相同的路由,就报promise uncaught(未捕获)
异常
<span style="background-color:#333333"><span style="color:#b8bfc6">// 方式一:
this.$router.push({path:'/index'},onComplete => {},onAbort => {})
// 方式二: 在组件内部, 在使用push的时候,需要使用catch来处理可能出现的异常,由于返回的是promise 所以可以catch
this.$router.push({name:'Home'}).catch(err => {})
// 方式三: 在index.js路由配置文件 统一进行全局处理
import Router from 'vue-router'
const originalPush =VueRouter.prototype.push
VueRouter.prototype.push = function(location) {
// 重写了原型上的push方法,统一的处理了错误信息
return originalPush.call(this, location).catch(err => err)
}</span></span>
6.13.3 组件中的beforeRouteEnter 路由不触发的问题
1、确保该组件挂载在router.js 里面。 2、没挂载在router.js 里面的组件如果需要监听路由的话,用watch 监听 $ route 就行了。(测试还是需要设置子路由)
<span style="background-color:#333333"><span style="color:#b8bfc6">watch:{
'$route':function(to, from,next) {
console.log('路由发生');
next()
}
},</span></span>
6.13.4 router-link 的问题
父组件的router-link
路径是top/playlist
, 子组件的rouer-link
设置路径为detail
时会被解析为top/detail
,此处需要设置为 <router-link :to="'../detail?id='+item.id">
6.14 vue-router 的两种模式
前面说过改变路径的方式有两种:
-
URL 的 hash
-
HTML5 的 history
前端路由的核心,就在于:改变视图的同时不会向后端发出请求。hash和history这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改是,虽然改变了当前的URL,但你浏览器不会立即向后端发送HTTP请求,对后端完全没有影响,因此改变hash不会重新加载页面。。
默认情况下,Vue 路径的改变使用的是 URL 的 hash,这样显示出的页面的地址中有一个 #
号,不太美观:
可以使用 HTML5 的 history 模式来进行改变,本质利用了HTML5 的 History 中新增的pushState() 和replaceState() 方法。(需要特定浏览器支持)进行如下配置即可:
router.js里面--------------->createRouter的属性
<span style="background-color:#333333"><span style="color:#b8bfc6">const router = createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
// hash模式:createWebHashHistory,history模式:createWebHistory
history: createWebHashHistory(),
routes, // `routes: routes` 的缩写
})</span></span>
history模式,会出现404 的情况,需要后台配置。
组件布局(移动端)
模拟tabbar
swiper