目录
14.5.3.为什么会出现这个知识点:捕获所有路由或404 Not found路由
1.前端渲染/后端渲染/前端路由/后端路由
1.1.后端渲染
WEB早期是由JSP+HTML+CSS组成,WEB服务器会把请求结果全部转化为HTML+CSS,这个时期的浏览器其工作就是把接收到的代码进行展示;
1.2.后端路由
后端服务器自己处理URL与页面之间的映射关系,早期的MVC模式,整个映射关系都配置在一个XML文件中;
1.3.后端路由缺点
①整个页面模块由后端人员来编写和维护的,早期的时候没有前端工程师,只有美工负责切图,然后剩下的工作全部交给后端人员来完成;
②需要嵌入JAVA等后端语言代码,整个页面既有HTML代码,也有CSS代码,同样还有后端语言代码;
1.4.前后端分离阶段
随着ajax的出现,这种开发模式有个突破:
①后端只提供API来返回数据,前端通过ajax收到数据后,自己通过js来将数据渲染到THML中;
②前后端责任分离,使得后端只注重数据,前端只注重交互与可视化;
③即便IOS/Android出现后,后端不需要进行任何修改;
1.5.前端渲染
HTML+CSS从静态资源服务器来获取,然后前端浏览器自己通过ajax来获取数据,然后数据的渲染是自己来完成;
1.6.前端路由与SPA单页面富应用阶段
这里随着ajax出现,使得前后端分离,然后更进一步发展出现了SPA(simple page web application)阶段,即单页面富应用;
之前,一个WEB应用会有N多个Html文件以及与Html文件配套的CSS、JS文件一起来组成;
到了“单页面富应用”阶段,整个WEB应用只有1个Html文件,甚至只有1个js、1个css文件组成,剩下的所有的展示、渲染等工作,全部交给js代码来完成;
(这里可以参考webpack案例工程,其打包的时候,会把全部js、全部css、一部分小图片全部融合到一个js文件中,这就很恐怖了)
为了实现这种效果,这里就需要前端路由了,其功能是:当用户点击某个按钮的时候,前端路由会根据该按钮对应的url查找其要展示的内容,然后把要展示的内容渲染到入口index.html中的对应的区域;
(这里的“要展示的内容”看做是Vue的自定义组件,也就说:vue-router会根据URL找到对应的自定义组件,然后根据自定义组件的el属性找到index.html中的位置,最后再把template属性中的内容替换掉el属性绑定的html标签;)
2.页面刷新与前端路由
在上一节中我们得知,vue-router通过URL与组件的映射来实现了SPA的单页面富应用的效果,这就意味着当你按下F5后,刷新页面的代价是比较高的,这里就必须考虑到一件事情:当用户点击某个按钮,然后该按钮修改URL的同时,又不会触发浏览器去刷新整个页面;
要达到这种效果,有2种解决方法:
2.1.URL的hash模式
通过location.hash的方式来修改URL链接,例如:
修改之前:
修改之后:
URL也修改了,但页面没有刷新;
2.2.HTML5的history模式
history.pushState
history.pushState方法采用类似于栈的数据结构,即后进先出,可以通过history的back/go方法一层一层进行返回:
history.replaceState
这个是直接替换,而不是push压入:
history.go
history.back()等价于history.go(-1)
history.forward()等价于history.go(1)
3.基于CLI2的vue-router安装与配置
vue-router是vue.js的官方插件,与vue.js深度集成,官方地址:https://router.vuejs.org/
①安装vue-router:Npm install vue-router --save
②在模块中使用它:
第一步:导入路由对象,并且调用Vue.use(VueRouter)
第二步:创建路由实例,并且传入路由映射配置
第三步:在Vue实例中挂载创建的路由实例
定义路由配置src/router/index.js
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import home from '@/components/home' import page1 from '@/components/page1' import page2 from '@/components/page2' // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名 path: '/', name: 'HelloWorld', // 该路径对应的组件 component: home }, { // 路径名 path: '/home', // 该路径对应的组件 component: home },{ // 路径名 path: '/page1', // 该路径对应的组件 component: page1 }, { // 路径名 path: '/page2', // 该路径对应的组件 component: page2 } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
在入口Vue实例中挂载路由mainjs
import Vue from 'vue' import App from './App' // 这里并没有详细说出/router/index.js文件,那是因为Vue默认找的就是该目录下名字叫index的js文件 // 第四步:将路由router挂载到Vue实例中 import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', // 第四步:将路由router挂载到Vue实例中 router, render: h => h(App) }) |
在入口组件App.vue中排版
<template> <div id="app"> <!-- <img src="./assets/logo.png">--> <router-view/> <router-link to="/home">home.js</router-link> <router-link to="/page1">page1.js</router-link> <router-link to="/page2">page2.js</router-link> <table> <tr> <td>测试router-view的排版布局</td> <td><router-view></router-view></td> </tr> </table> </div> </template> <script> export default { name: 'App' } </script> <style> </style> |
4.路由默认值
定义路由配置src/router/index.js
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import home from '@/components/home' import page1 from '@/components/page1' import page2 from '@/components/page2' // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 component: home },{ // 路径名 path: '/page1', // 该路径对应的组件 component: page1 }, { // 路径名 path: '/page2', // 该路径对应的组件 component: page2 } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
5.修改路由模式
默认是URL的hash模式,特征就是URL总是带有“#”号,这里通过Router的mode属性来修改:
定义路由配置src/router/index.js
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import home from '@/components/home' import page1 from '@/components/page1' import page2 from '@/components/page2' // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 从URL的hash模式,变成history模式 mode: 'history', // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 component: home },{ // 路径名 path: '/page1', // 该路径对应的组件 component: page1 }, { // 路径名 path: '/page2', // 该路径对应的组件 component: page2 } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
6.<router-link/>详解
to:用于指定跳转的路径;
tag:可以指定<router-link/>之后渲染成什么组件,比如可以渲染成一个button、div、a等标签;
replace:replace不会留下history记录,用replace取代了pushState;
active-class:当<router-link/>对应的路由匹配成功时,会自动给当前元素设置一个router-link-active的class样式,设置active-class可以修改默认的样式,即当你点击<router-link/>后呈现的样式;
定义路由配置src/router/index.js
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import home from '@/components/home' import page1 from '@/components/page1' import page2 from '@/components/page2' // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 一次性全部修改完router中匹配后,router-link颜色 linkActiveClass: 'active', // 从URL的hash模式,变成history模式 mode: 'history', // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 component: home },{ // 路径名 path: '/page1', // 该路径对应的组件 component: page1 }, { // 路径名 path: '/page2', // 该路径对应的组件 component: page2 } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
在入口组件App.vue中排版
<template> <div id="app"> <!-- <img src="./assets/logo.png">--> <router-view/> <!-- /home这里的active-class的样式,覆盖了Vue自己的router-link-active样式 --> <router-link to="/home" tag="button" replace active-class="active">home.js</router-link> <router-link to="/page1" tag="li" replace>page1.js</router-link> <router-link to="/page2" replace>page2.js</router-link> <table> <tr> <td>测试router-view的排版布局</td> <td><router-view></router-view></td> </tr> </table> </div> </template> <script> export default { name: 'App' } </script> <style> .router-link-active { color: red; } .active { color:blue; } </style> |
7.通过代码跳转路由
router组件已经注册到Vue中,因此,你可以在任何一个组件中通过this.$router方式来获取router对象,进而调用push、replace方法来进行跳转:
this.$router.push('/xxx);
this.$router.replace('/xxx);
在入口组件App.vue中排版
<template> <div id="app"> <!-- <img src="./assets/logo.png">--> <router-view/> <!-- /home这里的active-class的样式,覆盖了Vue自己的router-link-active样式 --> <router-link to="/home" tag="button" replace active-class="active">home.js</router-link> <router-link to="/page1" tag="li" replace>page1.js</router-link> <!-- <router-link to="/page2" replace>page2.js</router-link>--> <button @click="page2Click">page2.js</button> <table> <tr> <td>测试router-view的排版布局</td> <td><router-view></router-view></td> </tr> </table> </div> </template> <script> export default { name: 'App', methods: { page2Click(){ // this.$router.push('/page2'); this.$router.replace('/page2'); } } } </script> <style> .router-link-active { color: red; } .active { color:blue; } </style> |
8.动态路由
相比较于index.js文件,里面配置的“path”与“component”是明确一一对应的,但存在“path”不确定的情况,这种“path”与“component”匹配的关系,称为动态路由;
典型代表就是Restful风格的URL,例如,我做了一个用户列表/userlist/,如果我想查看具体某个人的信息则是类似这样的:/user/zhangsan、/user/lisi,这里就需要动态路由来完成;
动态路由的开发过程:在vue-router中配置动态path,其组成为:/xxx/:yyyy,这里的/xxx/就是path,而yyyy则是动态参数;然后就可以通过$route.params.yyyy来获取;
①定义动态路由
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import home from '@/components/home' import page1 from '@/components/page1' import page2 from '@/components/page2' import user from '@/components/User' // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 一次性全部修改完router中匹配后,router-link颜色 linkActiveClass: 'active', // 从URL的hash模式,变成history模式 mode: 'history', // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 component: home },{ // 路径名 path: '/page1', // 该路径对应的组件 component: page1 }, { // 路径名 path: '/page2', // 该路径对应的组件 component: page2 }, { // 这里配置了动态路由 path: '/user/:userid', // 该路径对应的组件 component: user } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
②在入口App.vue中定义动态path
<template> <div id="app"> <!-- <img src="./assets/logo.png">--> <router-view/> <!-- /home这里的active-class的样式,覆盖了Vue自己的router-link-active样式 --> <router-link to="/home" tag="button" replace active-class="active">home.js</router-link> <router-link to="/page1" tag="li" replace>page1.js</router-link> <!-- <router-link to="/page2" replace>page2.js</router-link>--> <button @click="page2Click">page2.js</button> <router-link v-bind:to="'/user/' + userid" tag="button" replace>zhangsan</router-link> <table> <tr> <td>测试router-view的排版布局</td> <td><router-view></router-view></td> </tr> </table> </div> </template> <script> export default { name: 'App', data(){ return { // 这里模拟从后台服务器获取到的数据库里面的数据 userid:'zhangsan' } }, methods: { page2Click(){ // this.$router.push('/page2'); this.$router.replace('/page2'); } } } </script> <style> .router-link-active { color: red; } .active { color:blue; } </style> |
③在具体页面User.vue获取传递过来的参数
<template> <div> <h2>userid:{{userid}}</h2> <h3>userid:{{$route.params.userid}}</h3> </div> </template> <script> export default { name: "User", computed:{ userid(){ // 注意:$router代表的是vue-router,而$route代表的是vue-router中的routes中匹配当前“path”的route return this.$route.params.userid; } } } </script> <style scoped> </style> |
9.vue-router打包与懒加载
按照CLI2的打包逻辑,其打包之后的目录结构如下:
我们会发现几个特点:
①与webpack相比,CLI2把js与css文件进行拆分,不再全部融合到一个bundle.js文件中;
②把bundle.js中的js代码分拆成3个不同的文件,每个文件承担一部分功能;
③app.xxx.js存放的是前端工程中的业务逻辑代码;
④manifest.xxx.js存放的是做底层支持的代码,例如:对于不支持做ES6的浏览器做支持处理等等,可以理解为针对不同浏览器做的底层兼容性处理;
⑤vendor.xxx.js则为第三方框架的代码,既然开发的时候用到了,那么,打包的时候就一起发布到服务器上;
但是,这种打包方式下会有一个问题:
按照CLI的约定,会把所有的业务代码全部打包到app.js文件中,这就导致一个app.js文件就非常大,当用户浏览器去加载这个文件的时候就变得非常慢,会导致用户浏览器会出现空白页;
为了解决这个问题,同时,考虑到vue-router是与URL进行绑定的,每个vue-router可以看做是一个业务模块的入口,因此,我们约定:由于一个vue-router就映射到一个组件,一个业务逻辑,因此,我们就把针对vue-router进行打包,一个vue-router打包成一个js文件,这样,当浏览器用户触发某个URL后,再让浏览器去加载该URL对应的vue-router.js文件;
那么怎么去配置CLI来按照vue-router来打包呢?
①结合Vue的异步组件、webpack打包:
const home = resolve =>{require.ensure([‘home.vue’],()=>{resolve(require(‘home.vue’))})}
②AMD写法:
const home = resovle -> require([‘home.vue’],resolve)
③在ES6中,使用import语法:
const home = () => import(‘home.vue’)
下面是自定义vue-router
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' // import home from '@/components/home' // import page1 from '@/components/page1' // import page2 from '@/components/page2' // import user from '@/components/User' // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 一次性全部修改完router中匹配后,router-link颜色 linkActiveClass: 'active', // 从URL的hash模式,变成history模式 mode: 'history', // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 // component: home component: () => import('@/components/home') },{ // 路径名 path: '/page1', // 该路径对应的组件 // component: page1 component: () => import('@/components/page1') }, { // 路径名 path: '/page2', // 该路径对应的组件 // component: page2 component: () => import('@/components/page2') }, { // 这里配置了动态路由 path: '/user/:userid', // 该路径对应的组件 // component: user component: () => import('@/components/User') } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
打包之后的效果,正好4个import对应4个js文件:
10.vue-router嵌套路由
2个URL,例如:/home/info1、/home/info2,这样的话,这2个URL共同属于/home/,我希望我访问/home/info1时,分别渲染/home/和/home/info1;
开发步骤如下:
①创建对应的子组件,并且在vue-router中配置对应的子路由;
②在组件内部使用<router-view/>标签;
例如:
①分别创建2个子组件:info1和info2:
<template> <div> /home/info1 </div> </template> <script> export default { name: "info1" } </script> <style scoped> </style> |
<template> <div> /home/info2 </div> </template> <script> export default { name: "info1" } </script> <style scoped> </style> |
②在vue-router中配置对应的子路由:
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' // import home from '@/components/home' // import page1 from '@/components/page1' // import page2 from '@/components/page2' // import user from '@/components/User' // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 一次性全部修改完router中匹配后,router-link颜色 linkActiveClass: 'active', // 从URL的hash模式,变成history模式 mode: 'history', // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 // component: home component: () => import('@/components/home'), children: [ { // 这里配置/home的默认展示界面 path: '', redirect: 'info1' }, { // 这里的path不用"/"来作为开头 path: 'info1', component: () => import('@/components/info1') }, { // 这里的path不用"/"来作为开头 path: 'info2', component: () => import('@/components/info2') } ] },{ // 路径名 path: '/page1', // 该路径对应的组件 // component: page1 component: () => import('@/components/page1') }, { // 路径名 path: '/page2', // 该路径对应的组件 // component: page2 component: () => import('@/components/page2') }, { // 这里配置了动态路由 path: '/user/:userid', // 该路径对应的组件 // component: user component: () => import('@/components/User') } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
③由于是在/home/下配置的info1和info2,因此,我们就需要在home.vue中配置info1和info2需要展示的位置,即在组件内部使用<router-view/>标签:
<template> <div> <h2>这是home页面</h2> <span>这里的to要用"/"来作为开头,还得用/home/开头,只能用绝对路径才行,因为相对路径的话,系统也不知道你会把系统路径放在哪里</span> <router-link to="/home/info1">info1</router-link> <router-link to="/home/info2">info2</router-link> <router-view></router-view> </div> </template> <script> export default { name: "home" } </script> <style scoped> </style> |
11.vue-router传递参数
vue-router传递参数,根据参数位置不同,大致分为2种类型:
传递方式 | 配置路由格式 | 传递的方式 | 传递后形成的路径 |
params类型 | /router/:id | 在path后面跟上对应的值 | /user/zhangsan /user/lisi |
query类型 | /router,也就是普通配置 | 对象中使用query的key作为传递方式 | /user?name=wangsu /user?name=lisi |
下面我们介绍下“query”类型是如何传递参数的:
①定义query1.vue
<template> <div> <h2>测试query类型的传递方式</h2> </div> </template> <script> export default { name: "ParamTest" } </script> <style scoped> </style> |
②以配置普通vue-router方式来配置该path
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' // import home from '@/components/home' // import page1 from '@/components/page1' // import page2 from '@/components/page2' // import user from '@/components/User' const query1 = () => import('../components/query1') // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 一次性全部修改完router中匹配后,router-link颜色 linkActiveClass: 'active', // 从URL的hash模式,变成history模式 mode: 'history', // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 // component: home component: () => import('@/components/home'), children: [ { // 这里配置/home的默认展示界面 path: '', redirect: 'info1' }, { // 这里的path不用"/"来作为开头 path: 'info1', component: () => import('@/components/info1') }, { // 这里的path不用"/"来作为开头 path: 'info2', component: () => import('@/components/info2') } ] },{ // 路径名 path: '/page1', // 该路径对应的组件 // component: page1 component: () => import('@/components/page1') }, { // 路径名 path: '/page2', // 该路径对应的组件 // component: page2 component: () => import('@/components/page2') }, { // 这里配置了动态路由 path: '/user/:userid', // 该路径对应的组件 // component: user component: () => import('@/components/User') }, { // query方式的path path: '/querytest', component: query1 } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
③在入口App.vue中配置该path
<template> <div id="app"> <!-- <img src="./assets/logo.png">--> <!-- /home这里的active-class的样式,覆盖了Vue自己的router-link-active样式 --> <router-link to="/home" tag="button" replace active-class="active">home.js</router-link> <router-link to="/page1" tag="li" replace>page1.js</router-link> <!-- <router-link to="/page2" replace>page2.js</router-link>--> <button @click="page2Click">page2.js</button> <router-link v-bind:to="{path:'/querytest',query: {name:'wangwu',age:18}}" >测试query</router-link> <router-link v-bind:to="'/user/' + userid" tag="button" replace>zhangsan</router-link> <br/> <div> <router-view></router-view> </div> <!-- <table>--> <!-- <tr>--> <!-- <td>测试router-view的排版布局</td>--> <!-- <td><router-view></router-view></td>--> <!-- </tr>--> <!-- </table>--> </div> </template> <script> export default { name: 'App', data(){ return { // 这里模拟从后台服务器获取到的数据库里面的数据 userid:'zhangsan' } }, methods: { page2Click(){ // this.$router.push('/page2'); this.$router.replace('/page2'); } } } </script> <style> .router-link-active { color: red; } .active { color:blue; } </style> |
④修改query1.vue,获取传递过来的参数,此刻URL则变成这样了:http://localhost:8080/querytest?name=wangwu&age=18
<template> <div> <h2>测试query类型的传递方式</h2> <h2>{{$route.query}}</h2> <h2>{{$route.query.name}}</h2> <h2>{{$route.query.age}}</h2> </div> </template> <script> export default { name: "ParamTest" } </script> <style scoped> </style> |
⑤把入口App.vue中配置的path,改成click事件,从代码来触发请求
<template> <div id="app"> <!-- <img src="./assets/logo.png">--> <!-- /home这里的active-class的样式,覆盖了Vue自己的router-link-active样式 --> <router-link to="/home" tag="button" replace active-class="active">home.js</router-link> <router-link to="/page1" tag="li" replace>page1.js</router-link> <!-- <router-link to="/page2" replace>page2.js</router-link>--> <button @click="page2Click">page2.js</button> <router-link v-bind:to="'/user/' + userid" tag="button" replace>zhangsan</router-link> <!-- 这里使用v-bind:to只是为了让vue来解析{},而不是让vue-router把{}当做普通字符串来处理--> <router-link v-bind:to="{path:'/querytest',query: {name:'wangwu',age:18}}" >测试query</router-link> <button @click="queryClick">测试query</button> <br/> <div> <router-view></router-view> </div> <!-- <table>--> <!-- <tr>--> <!-- <td>测试router-view的排版布局</td>--> <!-- <td><router-view></router-view></td>--> <!-- </tr>--> <!-- </table>--> </div> </template> <script> export default { name: 'App', data(){ return { // 这里模拟从后台服务器获取到的数据库里面的数据 userid:'zhangsan' } }, methods: { page2Click(){ // this.$router.push('/page2'); this.$router.replace('/page2'); }, queryClick(){ this.$router.push({path:'/querytest',query: {name:'wangwu',age:18}}); } } } </script> <style> .router-link-active { color: red; } .active { color:blue; } </style> |
12.vue-router全局导航守卫
导航栏,这个效果就不多说了,类似于下面这种效果:
这里以如何添加SPA上的title为例进行说明,至于开发步骤呢(这里针对的是全局导航),分成2个步骤:
①在各个子模块中添加meta属性,来为自己的模块提供title来供全局导航来使用;
②在入口main.js中,为全局router配置全局导航守卫,下面是具体代码范例:
为各个子模块添加title数据:
// 导入Vue import Vue from 'vue' // 导入路由 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' // import home from '@/components/home' // import page1 from '@/components/page1' // import page2 from '@/components/page2' // import user from '@/components/User' const query1 = () => import('../components/query1') // 第一步:通过Vue.use(插件),来全局安装插件 Vue.use(Router) // 第二步:创建路由对象 export default new Router({ // 一次性全部修改完router中匹配后,router-link颜色 linkActiveClass: 'active', // 从URL的hash模式,变成history模式 mode: 'history', // 第三步:配置路由与组件之间的映射关系 routes: [ { // 路径名,首页这里默认就是/,这里注意下 path: '/', // 重定向前端工程的首页,注意这里写的是path路径,而不是component的名字 redirect: '/home' }, { // 路径名 path: '/home', // 该路径对应的组件 // component: home component: () => import('@/components/home'), // 这里的meta是元数据的意思,可以存放很多自定义的,关于本模块的个性化数据 meta: { title: 'home标题' }, children: [ { // 这里配置/home的默认展示界面 path: '', redirect: 'info1' }, { // 这里的path不用"/"来作为开头 path: 'info1', component: () => import('@/components/info1') }, { // 这里的path不用"/"来作为开头 path: 'info2', component: () => import('@/components/info2') } ] },{ // 路径名 path: '/page1', // 该路径对应的组件 // component: page1 component: () => import('@/components/page1'), // 这里的meta是元数据的意思,可以存放很多自定义的,关于本模块的个性化数据 meta: { title: 'page1标题' } }, { // 路径名 path: '/page2', // 该路径对应的组件 // component: page2 component: () => import('@/components/page2'), // 这里的meta是元数据的意思,可以存放很多自定义的,关于本模块的个性化数据 meta: { title: 'page2标题' } }, { // 这里配置了动态路由 path: '/user/:userid', // 该路径对应的组件 // component: user component: () => import('@/components/User'), // 这里的meta是元数据的意思,可以存放很多自定义的,关于本模块的个性化数据 meta: { title: '动态路由params标题' } }, { // query方式的path path: '/querytest', component: query1, // 这里的meta是元数据的意思,可以存放很多自定义的,关于本模块的个性化数据 meta: { title: 'query标题' } } ] }) // 第四步:将路由router挂载到Vue实例中 // 第五步:<router-link/>显示链接和<router-view/>自定义组件显示位置 |
在入口main.js中,为router配置全局导航守卫
import Vue from 'vue' import App from './App' // 这里并没有详细说出/router/index.js文件,那是因为Vue默认找的就是该目录下名字叫index的js文件 // 第四步:将路由router挂载到Vue实例中 import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', // 第四步:将路由router挂载到Vue实例中 router, render: h => h(App) }) // 前置钩子函数,跳转之前进行回调 router.beforeEach(function (to,from,next) { console.log('-----------beforeEach------'); // 这种调用方法存在一个问题:路由嵌套,当嵌套路由作为一个前端工程首页的时候,并没有显示title // document.title = to.meta.title; // console.log(to) // 通过F12查看,嵌套路由to的meta属性中并没有title属性,反而在matched数组中的[0]的meta属性中存在title属性 // 其他普通路由里的meta属性与matched数组的[0]中均有title属性,所以,这里改进为从matched数组中获取 document.title = to.matched[0].meta.title; // 必须手动调用next,否则router导航没有任何点击效果 next(); }); // 后置钩子函数,即跳转完毕后再去回调 router.afterEach(function (to, from) { console.log('-----------afterEach------'); }); |
这里解释一下为什么不能使用to.meta.title的原因,看下图:
遇到嵌套路由的时候,跳到首页时会出现这种情况,然后通过F12查看发现:
所以,这里改用matched数组的形式;
12.1.扩展
①如果是后置钩子函数,即afterEach,就不需要主动调用next()函数
②前置钩子函数称为全局守卫,同时根据定义位置的不同,还分别存在:路由独享的守卫,组件内的守卫;
下面是官方说明:
13.keep-alive与vue-router
由于vue-router是一个组件,如果直接被包裹在keep-alive里面,那么所有路径匹配到的视图组件都会被缓存起来;
keep-alive也是Vue内置的一个组件,可以使得被包含的组件保留其状态,来避免重新渲染;
<keep-alive> <router-view></router-view> </keep-alive> |
注意:
①当使用keep-alive来包裹组件的时候,该组件的activated、deactivated才会生效起作用,否则不会起作用;
②<keep-alive/>有2个属性:include和exclude,exclude用来说明哪个vue-route不会被缓存,而include则是说明哪个vue-router会被缓存,取值来自自定义组件的name属性值,例如:
<template> <div> <h2>这是page1页面</h2> </div> </template> <script> export default { name: "page1" } </script> <style scoped> </style> |
<template> <div id="app"> <!-- <img src="./assets/logo.png">--> <!-- /home这里的active-class的样式,覆盖了Vue自己的router-link-active样式 --> <router-link to="/home" tag="button" replace active-class="active">home.js</router-link> <router-link to="/page1" tag="li" replace>page1.js</router-link> <!-- <router-link to="/page2" replace>page2.js</router-link>--> <button @click="page2Click">page2.js</button> <router-link v-bind:to="'/user/' + userid" tag="button" replace>zhangsan</router-link> <!-- 这里使用v-bind:to只是为了让vue来解析{},而不是让vue-router把{}当做普通字符串来处理--> <router-link v-bind:to="{path:'/querytest',query: {name:'wangwu',age:18}}" >测试query</router-link> <button @click="queryClick">测试query</button> <br/> <!-- 该属性里面不要加空格--> <keep-alive exclude="page1,User"> <router-view></router-view> </keep-alive> <!-- <table>--> <!-- <tr>--> <!-- <td>测试router-view的排版布局</td>--> <!-- <td><router-view></router-view></td>--> <!-- </tr>--> <!-- </table>--> </div> </template> |
14.扩展:Vue Router官网读后感
14.1.Vue Router与Vue关系
①是Vue.js官方的路由管理器;
②Vue Router是Vue.js(opens new window)官方的路由管理器。它和Vue.js的核心深度集成,让构建单页面应用变得易如反掌;
14.2.官方链接
https://router.vuejs.org/zh/installation.html
14.3.官方案例
<script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div> |
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) // 1. 定义 (路由) 组件。 // 可以从其他文件 import 进来 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定义路由 // 每个路由应该映射一个组件。 其中"component" 可以是 // 通过 Vue.extend() 创建的组件构造器, // 或者,只是一个组件配置对象。 // 我们晚点再讨论嵌套路由。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 创建 router 实例,然后传 `routes` 配置 // 你还可以传别的配置参数, 不过先这么简单着吧。 const router = new VueRouter({ routes // (缩写) 相当于 routes: routes }) // 4. 创建和挂载根实例。 // 记得要通过 router 配置参数注入路由, // 从而让整个应用都有路由功能 const app = new Vue({ router }).$mount('#app') // 现在,应用已经启动了! |
// Home.vue export default { computed: { username() { // 我们很快就会看到 `params` 是什么 return this.$route.params.username } }, methods: { goBack() { window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/') } } } |
14.4.案例总结
①<router-link/>类似于一个导航栏;
②<router-view/>现实自定义组件内容;
③通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由;
14.5.动态路由匹配
14.5.1.为什么会出现这个知识点?
按照RestFul风格,其中的GET请求很多都是下面形式,例如:
①/user/zhangsan
②/user/lisi
为了解决这类问题,出现了“动态路径参数”的技术,用该技术可以整合这类URL,例如:/user/:id,动态路径参数都是以冒号开头
一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到this.$route.params,可以在每个组件内使用。于是,我们可以更新模板,输出当前用户的ID:$route.params.id
14.5.2.为什么会出现这个知识点:响应路由参数的变化
当我从/user/foo导航到/user/bar时,为了提高效率,vue-router旧组件会被复用,这就导致组件的生命周期函数无法被回调;
但是,我就是想在导航切换的时候,去回调组件的生命周期,该如何解决呢,解决方法有2个:
①用watch(监测变化)$route对象;
②使用2.2中引入的beforeRouteUpdate导航守卫;
14.5.3.为什么会出现这个知识点:捕获所有路由或404 Not found路由
常规参数只会匹配被/分隔的URL片段中的字符,但如果想匹配任意路径,我们可以使用通配符(*);
当我们在vue文件中,定义路由时,特别是使用*通配符路由时,需要把*通配符路由放在最后,其他具体路由则放在前面;
*通配符路由通常用于客户端404错误,就是避免那些别有用心的人去探测应用的URL路径信息;
注意:当使用一个通配符时,$route.params内会自动添加一个名为pathMatch参数,它包含了URL通过通配符被匹配的部分;
14.5.4.为什么会出现这个知识点:高级匹配模式
vue-router使用的是path-to-regexp来作为路径匹配引擎,因此,如果目前信息无法满足你的要求,则可以通过这个官网去了解一下高级用法;
14.5.5.为什么会出现这个知识点:匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:路由定义得越早,优先级就越高
14.6.编程式的导航
14.6.1.router.push
<router-link>内部是通过router.push方法来实现内部逻辑的,那么,换而言之,我们可以通过router.push来实现编程式的导航;
①方法定义如下:router.push(location, onComplete?, onAbort?)
②在Vue实例内部,可以通过$router访问路由实例;
③想要导航到不同的URL,则使用router.push方法。这个方法会向history栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的URL;
注意:
①path与params不能共存,如果提供了path,params会被忽略,所以,要么是手写完整的带有参数的 path,要么是name + params组合;
②在2.2.0+,可选的在router.push或router.replace中提供 onComplete和onAbort回调作为第二个和第三个参数,其中onComplete将会在导航成功完成(在所有的异步钩子被解析之后) 时调用,onAbort将在终止(导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由)时调用
③在3.1.0+,可以省略第二个和第三个参数,此时如果支持 Promise,router.push或 router.replace将返回一个 Promise;
④如果目的地和当前路由相同,只有参数发生了改变(比如从一个用户资料到另一个/users/1->/users/2),需要使用 beforeRouteUpdate来响应这个变化;
14.6.2.router.replace
跟router.push很像,唯一的不同就是,它不会向history添加新记录,而是替换掉当前的history记录;
14.6.3.router.go
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n);
14.6.4.操作History
router.push、 router.replace和router.go跟 window.history.pushState、window.history.replaceState和window.history.go 好像, 实际上它们确实是效仿window.history API;
还有值得提及的,Vue Router 的导航方法(push、 replace、 go)在各类路由模式(history、 hash 和 abstract)下表现一致;
14.7.命名路由
通俗来讲,就是给路由对象起一个别名,通过name属性,给这个router实例起个别名;
14.7.1.方式1:$route.params.XXX
这种方式的缺点在于让组件只能在某些特定的URL上使用;
14.8.命名视图
同一个层级内,同时展示多个视图,通过router实例的components属性来指定多个子组件,然后再通过<router-view name="XXXX"/>来引用;
14.9.重定向和别名
重定向也是通过router实例的routes配置来完成,通过routes是一个数组,数组元素的redirect属性来指定重定向地址;
redirect属性取值可以是字符串,也可以是一个命名的路由,甚至是一个方法来动态返回重定向目标;
而别名是指什么呢?
{ path: '/a', redirect: '/b' }这里,当我在浏览器中输入/a的时候,vue-router会把/a转换成/b,然后拿着/b去查找匹配的路由;
{ path: '/a', component: A, alias: '/b' }而别名的是这样的:当我在浏览器输入/b,但这个/b的本体是/a,所以,vue-router却是按照/a去匹配的路由,相当于/a有2种访问方式:/a、/b;
14.10.路由组件传参
一个是VueRouter实例,一个是Vue实例,那么,VueRouter实例是如何向Vue实例传递参数的呢?
14.10.1.方式2:props解耦
在Vue实例中通过props属性定义要传递的参数,在VueRouter实例中为每个使用的组件中都添加props属性:
①当props被设置为true,route.params将会被设置为组件属性;
②当props是一个对象,它会被按原样设置为组件属性,当 props是静态的时候有用;
③当props是一个函数,这样便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等;
请尽可能保持props函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义props需使用包装组件,这样 Vue 才可以对状态变化做出反应。