什么是vue-router?
网址:起步 | Vue Router
前端路由有哪些?
SPA单页面应用:
实现SPA(single page application)单页面应用:一个项目只有一个页面,我们基于路由,控制页面显示不用的代码片段(或组件),当展示的内容改变的时候,页面并不会刷新! !
- 移动端大部分都是(追求原生app的操作体验)
- pc端的管理系统
MPA多页面应用:
MPA(multi page application)多页面应用:一个项目有很多页面,我们做的是页面之间的跳转,每一次跳转都是打开一个新的页面(相当于)页面刷新了)! !
- pc端的非管理系统的产品
-
搜索引擎优化:基于JS动态绑定的内容(Vue框架),在页面源代码中是看不到内容的,不利于SEO搜索引擎优化,想做SEO,需要服务器渲染(前后端不分离,现在主流的服务器渲染SSR:node + vue(nuxt.js) + react(next.js))
1. 首屏服务器渲染 「骨架屏」
2. 其余屏幕还是交给客户端渲染
单页面&&多页面对比图
前端 路由的两大模式
哈希路由(hash router):监听URL地址后面的HASH值
- 样式:
http://www.xxx/com/index.html#home
- 原理:改变url的哈希值不会刷新页面,通过指定的哈希值使用
window.onhashchange
事件来控制更改页面渲染的内容
浏览器路由(browser/history router):基于h5中的history api实现的
- 样式:http://www.xxx.com/home
- 缺点:需要服务器的支持:(当手动刷新的时候,这个页面不存在,不要返回404,依然返回主页面内容)
- 原理:通过history.pushState()方法改变网址,页面不会刷新,再通过window.onpopstate事件监听网址的改变
- vue脚手架基于webpack-dev-server启动服务(开发环境,已经完成了服务器应对history 模式支持的相关操作:但是打包到服务器上(生产环境),没有webpack-dev-server,此时服务器需要基于nginx做相同的配置支持)
区别
1 hash路由地址不好看,browser路由好看一点
2. 实现机制不同
- HASH路由每次跳转,修改的是URL的hash值(页面不刷新),基于监听onhashchange事件,获取最新的hash值,去路由规则表中,找到相对应的组件拿过来渲染,
- browser路由每次跳转修改的是url地址(页面不刷新history.pushState);监听popstate事件根据最新的地址找到相对应的组件渲染
- browser路由跳转的地址并不真实存在,所以页面手动刷新,会出现404,此时需要服务器的配合「后台:如果访问的地址不存在,返回主页内容」
vue-router使用
第一步: $ npm i vue-router
第二步:创建路由规则表
- 在src/router文件下创建路由存放的路径以及index.js 和routes.js文件:main.js注册使用路由
- 挂载之后每个组件都会有this.$router(实现路由跳转)&&this.$route(拿到路由信息)
方案一:写在index.js里
import Home from '@/views/Home.vue';
import login from '@/views/login.vue';
import order from '@/views/order.vue';
//创建index.js文件并配置vue-router的模式和路由规则表
//src/router/index.js :作用是创建vue路由实例,并配置路由
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)//导入vue vue-router,注册使用
const router = new VueRouter({
mode: 'hash', //设置路由模式 hash history(默认)
//设置路由匹配规则[路由表]
routes: [{
path: '/', //指定hash值(pathname值)
component: Home//渲染的组件
}, {
path: '/iogin',
component: login
}, {
path: '/order',
component: order
}, {
path: '*',//如果没有匹配项,
redirect: '/'//重定向到首页(也可以渲染一个404组件)
}]
});
export default router
方案二 写在routes.js里
1.把路由表抽离出来 ,放到routes.js里单独管理,并且导出
import Home from '@/views/Home.vue';
import login from '@/views/login.vue';
import order from '@/views/order.vue';
const routes = [{
path: '/',
component: Home
}, {
path: '/iogin',
component: login
}, {
path: '/order',
component: order
}, {
path: '*',
redirect: '/'
}]
export default routes
2.在index导入挂载
import routes from './routes.js'
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'hash',
routes
});
export default router
第三步:实现路由跳转(改变URL地址 或者哈希值)
注意:每一次的路由切换,都是把上一个渲染的组件释放(销毁beforeDestory-->destroyed) ,把新匹配的组件进行渲染(beforeCreate-->created-->beforeMount-->mounted)
第四步:设置一个容器,可以在指定的位置,把基于路由规则匹配的组件进行渲染
- 以下的
<router-view></router-view>
就是我们指定的组件渲染的位置
<template>
<div id="app">
<div class="navBox">
<!-- 内置组件(路由跳转.切换组件),
点击实现路由跳转,基于"to"属性指定跳转地址
页面渲染的时候会把router-link渲染为a标签
-->
<router-link to="/">首页</router-link>
<router-link to="/login">登录页</router-link>
<router-link :to="{ path: '/order' }">订单页</router-link>
</div>
<main class="mainBox">
<!--
内置组件(路由容器),渲染"基于"路由规则匹配的组件
当页面刷新或路由跳转后,vue都会拿URL最新地址(或哈希值)去路由表中进行匹配,
把匹配到的组件,放到router-view容器中渲染
-->
<router-view></router-view>
</main>
</div>
</template>
router-link
- ==router-link 内置组件(路由跳转/路由切换)==
-
实现页面跳转:点击实现路由跳转,基于“to”属性指定跳转地址
- 页面渲染的时候,会把router-link渲染为a标签
<router-link to="/">首页</router-link>
<router-link to="/analyse">分析页</router-link>
<router-link :to="{ path: '/order' }">订单页</router-link>
router-view
- ==router-view 内置组件(路由容器)==:用来渲染“基于路由规则匹配组件的” :
- 当页面刷新或者路由跳转后,vue都会拿url最新的地址(或者hash值)去路由表中进行匹配,把匹配到的组件,放到
router-view
容器中渲染
- 而每一次的路由切换,都是把上一个渲染的组件释放(或销毁
beforeDestroy=>destroyed
),把新匹配的组件进行渲染(beforeCreate->created->beforeMount->mounted)
<main class="main-box">
<router-view></router-view>
</main>
精准匹配/非精准匹配/完全不匹配:
页面刷新或路由切换:都会拿最新的地址(或哈希值)与每一个router-link
中to属性的值(或者path属性值)进行匹配;完全“精准”匹配的会给A标签设置:router-link-exact-active router-link-acive
两个样式名;非精准匹配的只设置:router-link-active
这个样式类,一点都没匹配的,啥样式都不设置!!=>==我们后期可以基于这个特点,给当前匹配的导航设置选中特殊样式==
页面地址:/order
to的地址:
/ 非精准匹配 (包含一个完整的)
/ : 任何地址都包含一个完整的斜杠
/home2 VS /home 不算(完全不匹配)
/home/list VS /home 算(前面router-link的值的包含后面路由表里的值)
/analyse 完全不匹配
/order 精准匹配(一毛一样)
//------------------------------------------------
根据精准匹配设置样式
&.router-link-exact-active {
color: red;
}
二级路由
二级路由也是路由表的形式,只是里面的路径:
- 路由会对路由表中祖先级和父级,进行==非精准匹配==,对最低级做==精准匹配==
- 二级路由可以直接在一级路由表中配置,但这样会显得代码嵌套层数太多,我们可以将二级路由抽出来,放在一个js文件中,再进行导入引用:
import Home from '@/views/Home.vue';
import login from '@/views/login.vue';
import order from '@/views/order.vue';
import hoomeRoutes from './homeRoutes';
// 路由根据路由表时自上而下匹配的,只要有一个匹配就直接跳转,所以path:"*"不能在最前面匹配
const routes = [{
path: '/',
redirect: '/home'
}, {
path: '/home',
component: Home,
children: hoomeRoutes,//二级路由
},
{
path: '/login',
component: login
}, {
path: '/order',
component: order
}, {
path: '*',
redirect: '/'
}]
export default routes
-------------------------------------------------------------
- 如果不以斜杠开头,则表示拼接到父级路由后面
- 如果以斜杠开头,则表示井号#后面的全部路径,不会拼接父路由路径
//管理home模块的路由表(二级/三级路由)
import CustomList from '../views/Home/CustomList.vue';
import CustomAdd from '../views/Home/CustomAdd.vue';
import VisitList from '../views/Home/VisitList.vue';
const hoomeRoutes = [{
//如果/home后面没有跟路径,则默认将它重定向到'/home/customerlist',也就是让他第一次打开home就在custmerlist页面
path: '',
redirect: '/home/customlist'
}, {
path: 'customlist',//-->/Home/CustomList
component: CustomList
}, {
path: 'customadd',
component: CustomAdd
}, {
path: 'visitlist',
component: VisitList
}];
export default hoomeRoutes;
路由懒加载
vue前端性能优化之一:路由懒加载 分割打包按需导入
问题:如果在编写路由表的时候,事先导入了所有组件,根据规则渲染不同的组件,这样最后build打包的时候,会把所有组件全部打包到一个js中,js文件较大,页面第一次渲染请求js时间过长,延长了白屏事件~~~
解决:路由懒加载依托于ES6中提供的import函数
- 1、==webpack打包的时候实现代码切割==:在路由表中已经导入的组件(一般只导入默认需要展示的,就是第一次访问页面需要展示的),打包到一个js中,其余的组件根据情况,分割为一个或多个js文件!
- 2、最开加载页面,只把主JS文件导入,当路由匹配了某个规则,需要渲染某个组件,再把这个js文件加载
- 在路由表中各项的component属性后面用ES6函数:
()=>import (/* webpackChunkName: 'analyse'
的形式导入指定路径组件的就是==路由懒加载==
import Home from '@/views/Home.vue';
import hoomeRoutes from './homeRoutes';
const routes = [{
path: '/',
redirect: '/home'
}, {
//首页不做懒加载
path: '/home',
component: Home,
children: hoomeRoutes,//二级路由
},
//其余页面先不加载,匹配之后在加载
{
path: '/login',
component: () => import('@/views/login.vue')
}, {
path: '/order',
component: () => import('@/views/order.vue')
}, {
path: '*',
redirect: '/'
}]
export default routes
按组件打包js,按需导入
- =问题==:我们每通过
()=>import("@/pages/home/VisitList.vue")
的形式使组件路由懒加载,都会形成一个单独的js文件,这样就会导致项目中js文件很多。
- 在import内,最前面加上一句
/* webpackChunkName: 'analyse'*/
这样,它以及它子孙组件就会打包成一个js组件,实现了按包分割的功效,减少了js文件数量
{
path: '/login',
component: () => import(/* webpackChunkName: 'login' */'@/views/login.vue')
},
route与router的属性和方法
当我们"new Vue({router})",会给每一个组件(vue实例)注入两个私有属性
- this.$route:存储路由匹配信息
- this.$router: VueRoute的一个实例,可以调用VueRoute.prototype上的方法实现路由跳转
$route: 内部做了get劫持,可以监听到路由的变化
- fullPath完整的路由地址(含问号传参信息)
- path:不含问号号传参信息
- query:{}以键值对的方式存储了问号传参信息
- params:{}存储的是"路径传参"信息,或者是"隐式传参"信息
- name:路由名字
- meta:{}存储路由元信息
- matched:[...]记录路由具体匹配的规则记录
- hash:存储除hash路由内容外,单独设置的哈希值:
#home?xxx#aaa
,hash的值就是#aaa
$router:
私有属性方法
- mode:路由模式
- options.routes:路由表
- app:new vue时创建的组件
- matcher.getRoutes()路由表信息
- matcher.addRoutes动态添加路由表信息
公有属性方法:
push 跳转到指定路由 (this.$router.push('/login')),等同于<router-link to='/login>
对象写法:可以传参了
this.$router.push({
push.'/login'
})
- replace 也是跳转到指定的路由
- push是新增一条历史记录.而replace是修改本条记录
- go(n): 以当前历史记录为标准,前进或者后退, 基于n指定步数,go(-1)返回上一级, go(1)前进一步
- back: go(-1)
- forwarp: go(1)
- addRote addRoutes 动态向路由表中加入新的规则记录(动态路由)
- getRoutes获取现有的路由表
图解:
路由切换的传参方式
每一次路由切换想把一些信息传递给下一个组件
1.问号传参#/xxx?xxx=xxx
[丑&可以在地址栏中看到传递信息]
//AZ组件向B组件传参
//A组件
this.$router.push({
path:"/B",
query:{
lx:1,
from:"weixin"
}
})
//B组件--------------------
created() {
console.log(this.$route.query);
},
//基于this.$route.query获取即可->{lx:1,from:"weixin"}
------------
//因为地址栏中有,即使手动刷新B野蛮,信息还可以获取
2.隐式传参: #/xxx 地址栏中看不见,从内部把信息传递过去&&需要基于路由名字跳转,基于path跳转不可以
建议:后期在编写路由表时,给每一个路由设置一个名字'命名路由',后期实现路由跳转,可以不基于path跳转基于name也可以跳转
//A组件
this.$router.push({
name:"B"//用name指定要跳转到那个路由
path:"/B",
params:{
lx:1,
from:"weixin"
}
})
//B组件
created() {
console.log(this.$route.params);
},
//基于this.$route.params获取即可->{lx:1,from:"weixin"}
------------
因为传递的信息在地址栏中不存在,所以在b页面刷新,传递的信息就没有了
3.路径参数:把需要传递的数据,当做理由地址的一部分 例如#/xxx/100
[常用]因为不丑
第一步:设置路由表:xxx是设置动态规则
{//这种是不传参数时匹配的customadd路由路径
name:"home_customadd",
path: 'customadd',
component: () => import(/* webpackChunkName: "home" */'@/pages/home/CustomAdd.vue')
},
{//这种是需要传参数id和name匹配的路由路径
name:"home_customadd",
path: 'customadd/:id/:name',
component: () => import(/* webpackChunkName: "home" */'@/pages/home/CustomAdd.vue')
}
第二步:实现路由跳转
this.$router.push("/home/customadd/100/lisa");
第三步:获取
this.$router.params()->{id:"100",name:"lisa"};
案例 客户管理系统新增客户与编辑客户是同一个路由
<template>
<div class="CustomAdd">{{ title }}</div>
</template>
<script>
export default {
name: "CustomAdd",
//新增用户还是修改用户由title决定
data() {
return {
title: "新增客户",
};
},
//释放的上一个组件和即将渲染的下一个组件,是同一个组件,此时这个组件即不会被释放,也不会被重新渲染(也就是第一次渲染的逻辑就不会触发了,created就不会在执行了)
created() {
//存储路径传参或者隐式传参信息
let { id } = this.$route.params;
if (id) {
this.title = "修改客户";
}else {
this.title = "新增客户";
}
},
};
</script>
路由跳转的特殊情况:路由切换的时候,要释放的上一个组件和即将渲染的下一个组件,是同一个组件,此时这个组件即不会被释放,也不会被重新渲染(也就是第一次渲染的逻辑就不会触发了,例如:created就不会在执行了)
==解决方案一==:基于watch监听路由的变化监听路由的变化,从而做一些事情
watch: {
$route() {
//监控vm实例中的私有$route属性只有改变时执行
let { id } = this.$route.params;
if (id) {
this.title = "修改客户";
} else {
this.title = "新增客户";
}
},
},
---------------------------------------
watch: {
$route: {
handler() {
let { id } = this.$route.params;
if (id) {
this.title = "修改客户";
} else {
this.title = "新增客户";
}
},
immediate: true,//重新渲染组件(第一次渲染逻辑),让监听器处理
},
},
解决方案二:把需要依赖路由变化而变化的的信息,都设置为计算属性
computed: {
title() {
let { id } = this.$route.params;
return id ? "修改客户" : "新增课户";
},
},
VueRouter实例的属性
VueRouter的实例属性就是创建路由时可以添加的属性:
-
name:"名字"
:==命名路由==,设置路由表时必须加的属性,名字不能重复,后期实现路由跳转可以不基于path,基于name也可以跳转
-
path:"/home"
:表示在路由表中注册/home
路径的路由信息,当请求的hash值或地址为/home
时,就会去路由表中精准匹配到/home
路径下指定的组件,并进行切换
-
component:[组件]
:指定需要切换(渲染)的组件
-
redirect:"/list"
:将这个路径重定向到path为/list
的路径下面
-
children:[数组]
:二级路由,也如同组件,是数组形式一般二级路由会单独提出来,再导入
-
meta:{任何类型}
:路由元信息:记录每一个路由表的一些基础信息this.$router.meta
:获取
路由导航守卫
在每一次页面刷新(或者路由跳转),路由进行规则匹配的时候,也会触发一些钩子函数
A组件----B组件
- 1.释放A组价,触发A组件的
beforeRouterLeave
钩子函数(写A在组件中)
- .-----A组件释放,即将开启B组件的解析和渲染-----
- 2.触发全局前置守卫函数beforeEach(写在创建路由处)
- 3.如果A和B是相同组件则会触发组件的beforeRouterUpdate构子函数
- 4.在触发匹配路由表中的独享守卫
beforeEnter
【写在路由表中】
- -----b组件被激活,开始进入到b组件中进行渲染-----
- 5.触发B组件的beforeRouterEnter的构子函数
- 6.触发全局解析守卫beforeResolve
- 7.触发全局后置守卫afterEach
真实项目中,我们最常用的是 全局前置守卫"beforeEach",每一次页面刷新(或路由跳转),也不论从哪跳到哪,beforeEach一定会被触发! !
例如:我们一般会在beforeEach前置守卫中,做登录状态的校验(只有不是进入登录页,都需要在这校验一下用户是否登录,如果登录了,正常进行后面的步骤,如果没有登录,直接让其返回登录页)
代码
router.beforeEach((to, from, next) => {
//to:去哪 from:从哪来
//next:进行下一步[如果next不执行,卡在这结束了 后面啥都不做]
//next()之前规划咋走则,继续往下走,
// next('/login')直接走到某个指定的路由
let title = to.meta.title;
if (title) document.title = title
next()
})
面包屑导航
//---路由表中
{
name: 'homeCustomlist',
path: 'customlist',//-->/Home/CustomList
component: CustomList,
meta: {
title: "首页--客户列表",
nav: ['首页', '客户列表']
},
},
页面中------
<template>
<div class="CustomList">
{{ $route.meta.nav.join("/") }}
</div>
</template>
扩展
- 场景切换动画
-
keep-alive
:组件缓存
-
scrollBehavior(to,from,savePosition){return {x:0,y:0}}
:滚动行为,组件跳转的时候规定组件显示 滚动的位置