3、高级篇
前言
- 基础篇链接:https://www.cnblogs.com/xiegongzi/p/15782921.html
- 组件化开发篇链接:https://www.cnblogs.com/xiegongzi/p/15823605.html
3.1、回顾浏览器本地存储
-
就是localstorage和sessionstorage这两个( 前者浏览器关闭不会清空,后者关闭浏览器会清空 ),二者常用的API都一模一样,二者里面存的数据都是key-value的形式
- 存数据 setItem( 'key', 'value' ) 使用:LocalStorage.setItem( 'key' , 'value' )其他的都是差不多的
- 取数据 getItem( 'key' ) 注:若key不存在,则:此API返回值是null( 若key是对象字符串 那么用JSON.parse()转成对象 返回值也是null )
- 删除某一个数据 removeItem()
- 清空所有 clear()
- 二者存储的内容大小一般为5M的字符串( 不同浏览器可能会不一样 )
3.3、组件的自定义事件
3.3.1、绑定自定义事件
- 这里实现方式有两种:一种是用v-on搭配VueComponent.$emit实现【 ps:此种方式有点类似子传父 】;另一种是使用ref属性搭配mounted()来实现【 此种方式:复杂一点,但是更灵活 】
- 两种实现方式都可以,而二者的区别和computed与watch的区别很像【 ps:第二种方式可以实现异步操作,如:等到ajax发送请求得到数据之后,再进行事件绑定 】,接下来看实例,从而了解更明确点,用如下实例做演示【 ps:不用前面玩的子传父啊,但是:自行可以先回顾一下子传父的传统方式:父给子一个函数,子执行,参数即为父要得到的数据 】
3.3.1.1、v-on搭配emit实现
- v-on是可以简写的啊!!!!
3.3.1.2、ref搭配mounted实现
- 在前面实现的基础上,子组件代码不变,在父组件中加入如下的代码
-
mounted()中是可以进行异步操作的啊,所以才可以让这种自定义事件更灵活
-
另外:既然是事件,那么也可以使用事件修饰符:prevent、stop、once
- 在v-on中,这三者和以前一样的玩法,都是加在事件名后面即可,如:@zixeiqing.once = "xxxxx"
- 在ref中,是用在this.\(refs.person.\)on('zixieqing',this.demo )中的\(on这里的,once就是使用\)once,替换掉原来的$on
3.3.2、解绑自定义事件
-
这玩意用的就是VueComponent.$off( ['要解绑的事件名'] )这个内置函数来实现解绑的,
- 当然:数组[ ]中,如果是解绑单个事件,那么[ ]这个括号不要也行;
- 如果是解绑多个自定义事件,那么使用 , 逗号隔开即可;
- 另外:$off()不传递参数时,默认是把组件的所有自定义事件都解绑了【 ps:有一个解绑一个 】
- 自定义事件的核心话:给谁绑定事件,那么事件就在谁身上;给谁解绑自定义事件,那么就去谁身上解绑
- 另外:前面玩Vue生命周期的beforeDestroy时有一个小点只是简单提了一下,生命周期图如下
- 上图中的内容,有最后一点没有验证:
说在beforeDestroy中,会销毁子组件和自定义事件
- 说此时销毁自定义事件
- 而所谓的销毁子组件也就好理解了,就是把父组件销毁之后,那么:子组件也活不成了【 ps:要验证的话,可以使用销毁vm,然后看旗下的子组件还能活不?答案肯定是活不成的 】
3.3.3、自定义事件中的两大坑
子组件是如下的样子
1、在ref属性实现的方式中,关于this的指向问题
-
第一种就是将回调函数放到父组件的methods中
- 此种方式,会发现this指向的是父组件
-
第二种:将回调函数直接放到this.\(refs.people.\)on( 'event',xxxx ) ]中的xxxx中:
-
这种情况:会发现,this不再是父组件实例对象,而是子组件的实例对象,但是:可以让它变为子组件实例对象【 ps:把回调的普通函数写成兰姆达表达式就可以了 】
-
这种情况:会发现,this不再是父组件实例对象,而是子组件的实例对象,但是:可以让它变为子组件实例对象【 ps:把回调的普通函数写成兰姆达表达式就可以了 】
2、组件使用原生DOM事件的坑【 ps:了解native修饰符,学后端的人看源码的时候,对这个修饰符再熟悉不过了 】
3.3.4、自定义事件总结
-
自定义事件是一种组件间通信的方式,适用于:子组件 ——> 父组件通信
-
使用场景:想让子组件给父组件传递数据时,就在父组件中给子组件绑定自定义事件【 ps:事件回调在父组件methods / 其他地方 中 】,而要解绑自定义事件就找子组件本身
-
绑定自定义事件:
-
1、在父组件中:
<Person @zixieqing="demo"/>
或<Person v-on:zixieqing="demo"/>
-
2、在父组件中:
-
<Person ref = "demo"/> ......... methods: { test(){......} } ......... mounted(){ this.$refs.demo.$on('eventName',this.test) }
-
-
3、若想让自定义事件只能触发一次,可以使用once修饰符【 ps:使用v-on的方式实现的那种 】 或 $once【 ps:使用ref属性实现方式的那种 】
-
-
触发自定义事件:
this.$emit('eventName',sendData)
【 ps:给谁绑定自定义事件,就找谁去触发 】 -
解绑自定义事件:
this.$off(['eventName',.......])
【 ps:给谁绑定自定义事件,就找谁解绑;另:注意解绑事件是单个、多个、全解的写法 】 -
组件上也可以绑定元素DOM事件,但是:需要使用native修饰符
-
注意项:通过
this.$refs.xxxx.$on('eventName',回调)
绑定自定义事件时,回调要么配置在父组件的methods中,要么用兰姆达表达式【 ps:或箭头函数 】,否则:this执行会出现问题
3.4、全局事件总线
- 这玩意儿吧,不算知识点,是开发中使用的技巧而已,里面包含的只是在前面全都玩过了,只是:把知识做了巧妙的应用,把自定义事件变化一下,然后加上Vue中的一个内置关系
VueComponent.prototype._ _proto _ _ === Vue.prototype
从而实现出来的一个开发技巧 - 此技巧:可以实现任意组件间的通信
3.4.1、疏通全局事件总线逻辑
但是:现在把思路换一下来实现它
通过上面的分析图了解之后,就可以分析出:单独选取的那个组件需要具有如下的特性:
- 1、此组件能够被所有的组件看到
- 2、此组件可以调用\(on()、\)emit()、$off()
1、那么为了实现第一步:能够让所有的组件都看得到可以怎么做?
- 1)、使用window对象,可以做到( 但是:不推荐用 )
- 此种方式不推荐用呢,是因为:本来就是框架,谁还去window上放点东西呀,不是找事吗
-
2、就是利用Vue中的内置关系
VueComponent.prototype._ _proto _ _ === Vue.prototype
,即:公共组件选为Vue实例对象vm,这个诶之关系怎么来的,这里不再说明了,在基础篇VueComponent()中已经说明过了,利用此内置关系就是利用:VueComponent可以获取Vue原型上的属性和方法,同时选取了Vue实例对象之后,\(on()、\)emit()、$off()都可以调用了,这些本来就是Vue的内置函数啊,Vue实例对象还没有这些函数吗
3.4.2、全局事件总线实例
- 实现方式:vm + beforeCreate()【 ps:初始化嘛,让关系在一开始就建立 】 + mounted() + $on() + $emit() + beforeDestroy()【 ps:做收尾工作,解绑自定义事件 】+ $off()
实例演示:
-
- 注:上图中的\(bus是自己起的名字,但是开发中一般起的都是这个名字,bus公交车嘛,谁的都可以上,而且还可以载很多人,放到组件中就是:谁都可以访问嘛,加了一个\)是因为迎合Vue的设计,内置函数嘛,假装不是程序员自己设计的【 ps:实际上,bus还有总线的意思 】
3.4.3、全局事件总线总结
-
全局事件总线又名GlobalEventBus
-
它是一种组件间的通信方式,可以适用于任何组件间通信
-
全局事件总线的玩法:
-
1、安装全局事件总线
-
new Vue({ ....... beforeCreate(){ Vue.prototype.$bus = this }, ...... })
-
-
2、使用事件总线
-
发送数据:
this.$bus.$emit('EventName',sendData)
-
接收数据:A组件想接收数据,则:在A组件中给$bus绑定自定义事件,把事件的回调放到A组件自身中【 ps:靠回调来得到数据 】
-
// 使用methods也行;不使用,把回调放到$on()中也可以【 ps:推荐使用methods,因为不必考虑$on()中的this问题 】 methods: { sendData(){ ...... } }, ........ mounted(){ this.$bus.$on('eventName',receiveData) }, ....... beforeDestroy(){ this.$bus.$off([ 'eventName' , ..... ]) }
-
-
-
3.5、消息订阅与发布
什么是消息订阅与发布?
- 这个东西每天都见到,就是:关注,关注了人,那别人发了一个通知 / 文章,自己就可以收到,这就是订阅与发布嘛
-
这里使用pubsub-js这个库来演示( 使用其他库也行,这些玩意儿的思路都一样,这是第三方库啊,不是vue自己的 ),其中:
- pub 就是publish,推送、发布的意思
- sub 就是subscribe 订阅的意思
- 也就是:一方发布、一方订阅嘛,这个东西玩后端的人再熟悉不过了,Redis中就有消息订阅与发布,而且用的指令都是这两个单词,RabbitMQ也是同样的套路,只是更复杂而已
3.5.1、玩一下pubsub-js
基础代码
1、给项目安装pubsub-js库,指令:npm install pubsub-js
2、消息发布方
-
2.1、引入pubsub-js库
-
2.2、使用publish( 'msgName' , sendData )这个API进行数据发送
3、消息接收方
- 3.1、引入pubsub-js库
- 3.2、使用subscribe( 'msgName' , callback )这个API利用回调进行数据接收
-
3.3、关闭订阅
4、效果如下
3.5.2、消息订阅与发布总结
-
它是一种组件间通信的方式,适用于:任意组件间通信
-
使用步骤:
-
1、安装pubsub-js 指令:
npm install pubsub-js
-
2、消息接收方、发送方都要引入pubsub 代码;:
import pubsub from "pubsub-js"
-
数据发送方:
pubsub.publish('msgName',sendData)
-
数据接收方:
-
// methods可写可不写【 ps:推荐写,不用考虑this的指向问题,和自定义事件一样的 】 methods: { demo(){.....} } ........ mounted(){ // 使用this.msgName把每条订阅都绑在组件实例对象vc上,方便取消订阅时获取到这个订阅id this.msgName = pubsub.subscribe('msgName', callback) // 如果不写methods,那么回调就写在这里,注意:使用箭头函数 } ....... beforeDestroy(){ pubsub.unsubscribe( this.msgName ) }
-
-
-
3.6、插槽
1、基础代码
3.6.1、默认插槽
- 此种插槽适合只占用一个位置的时候
需求、让食品分类中显示具体的一张食品图片、让电影分类中电视某一部具体的电影,使用默认插槽改造
-
<template> <div class="container"> <Category title = "食品"> <!-- 2、将内容套在组件标签里面从而携带到slot处 此种方式:是vue在解析完App这个组件的模板时, 将整个组件中的内容给解析完了,然后放到了Category组件里面使用slot占位处 所以:slot所放位置 和 这里面代码解析之后放过去的位置有关 另外:由于是先解析完APP组件中的东西之后 再放到 所用组件里面slot处的位置 因此:这里可以使用css+js,这样就会让 模板 + 样式解析完了一起放过去 不用css+js就是先把模板解析完了放过去,然后找组件里面定义的css+js --> <img src="./assets/food.png" alt="照片开小差去了"> </Category> <Category title = "游戏"> <ul> <li v-for=" (game,index) in games" :key="index">{{game}}</li> </ul> </Category> <Category title = "电影"> <video controls src="./assets/枕刀歌(7) - 山雨欲来.mp4"></video> </Category> </div> </template> <script> import Category from "./components/Category.vue" export default { name: 'App', components: {Category}, data() { return { foods: ['紫菜','奥尔良烤翅','各类慕斯','黑森林','布朗尼','提拉米苏','牛排','熟寿司'], games: ['王者荣耀','和平精英','英雄联盟','文明与征服','拳皇','QQ飞车','魔兽争霸'], filems: ['无间道','赤道','禁闭岛','唐人街探案1','肖申克的救赎','盗梦空间','无双'] } }, } </script> <style> .container { display: flex; justify-content: space-around; } img,video { width: 100%; } </style>
3.6.2、具名插槽
- 指的就是有具体名字的插槽而已,也就比默认插槽多了两步罢了
- 在使用slot进行占位时就利用name属性起一个名字,然后在传递结构时使用slot="xxx"属性指定把内容插入到哪个插槽就OK了
需求、在电影分类的底部显示"热门"和"悬疑"
3.6.3、作用域插槽
- 这个玩意儿的玩法和具名插槽差不多,只是多了一点步骤、多了一个要求、以及解决的问题反一下即可
基础代码
需求:多个组件一起使用,数据都是一样的,但是有如下要求:
- 数据显示时要求不是全都是无序列表,还可以用有序列表、h标签......
- 要求data数据是在子组件中【 ps:就是Category中 】,而结构需要写在父组件中【 ps:就是App中 】,也就是父组件要拿到子组件中的data
- 利用前面已经玩过的子父组件通信方式可以做到,但是麻烦。因此:改造代码
开始改造代码:
-
第一步:提data
- 查看效果:
- 可能会想:明明可以将data放到父组件中 / 将结构ul放到子组件中,从而实现效果,为什么非要像上面那有折腾,没事找事干?开发中有时别人就不会把数据交给你,他只是给了你一条路,让你能够拿到就行
- 查看效果:
-
第二步:使用作用域插槽进行改造
既然作用域插槽会玩了,那就实现需求吧
另外:父组件接收数据时的scope还有一种写法,就是使用slot-scope
3.6.4、插槽总结
1、作用:让父组件可以向子组件指定位置插入HTML结构,也是一种组件间通信的方式,适用于:父组件 ===》 子组件
2、分类:默认插槽、具名插槽、作用域插槽
3、使用方式
-
1)、默认插槽
-
// 父组件 <Category> <div> HTML结构 </div> </Category> // 子组件 <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容</slot> </div> </template>
-
-
2)、具名插槽
-
// 父组件 <template> <!-- 指定使用哪个插槽 --> <Category slot = "footer">也可以加入另外的HTML结构</Category> </template> // 子组件 <template> <div> <!-- 定义插槽 并 起个名字--> <slot name = "footer">插槽默认内容</slot> </div> </template>
-
-
3)、作用域插槽
-
// 子组件 <template> <div class="category"> <h3>{{title}}分类</h3> <!-- 作用域插槽 1、子组件( 传递数据 ) 提供一个路口,把父组件想要的数据传给它【 ps:有点类似于props的思路,只是反过来了 】 :filems中的filems就是提供的路,给父组件传想要的东西【 ps:对象、数据都行 】 --> <slot :filems = "filems">这是默认值</slot> </div> </template> <script> export default { name: 'Category', props: ['title'], data() { // 数据在子组件自身中 return { filems: ['无间道','赤道','禁闭岛','唐人街探案1','肖申克的救赎','盗梦空间','无双'] } }, } </script> // 父组件 <template> <div class="container"> <Category title = "电影"> <!-- 2、父组件( 接收数据 ) 前面说的 多了一个要求 就是这里"必须用template标签套起来" 怎么接收?使用scope="xxxx"属性 xxx就是接收到的数据,这个名字随便取 这个名字不用和子组件中用的 :filems 这个filems这个名字保持一致,因:它接收的就是这里面传过来的东西 但是:这个数据有点特殊,需要处理一下 --> <template scope="receiveData"> <!-- {{receiveData}} --> <!-- 拿到了数据,那"页面的结构就可以随插槽的使用者随便玩"了 --> <ul> <li v-for="(filem,index) in receiveData.filems" :key="index">{{filem}}</li> </ul> </template> </Category> <Category title = "电影"> <!-- ES6中的"结构赋值"简化一下 --> <template scope="{filems}"> <ol> <li v-for="(filem,index) in filems" :key="index">{{filem}}</li> </ol> </template> </Category> <Category title = "电影"> <!-- ES6中的"结构赋值"简化一下 --> <template scope="{filems}"> <h4 v-for="(filem,index) in filems" :key="index">{{filem}}</h4> </template> </Category> <!-- scope还有一种写法 使用slot-scope--> <Category title = "电影"> <!-- ES6中的"结构赋值"简化一下 --> <template slot-scope="{filems}"> <h4 v-for="(filem,index) in filems" :key="index">{{filem}}</h4> </template> </Category> </div> </template> <script> import Category from "./components/Category.vue" export default { name: 'App', components: {Category}, } </script>
-
3.7、Vuex
- 概念:在Vue实例中集中式状态( 数据,状态和数据是等价的 )管理的一个插件,说得通俗一点就是:数据共享,对多个组件间的同一个数据进行读/写,不就是集中式管理了吗( 对立的观点就是"分布式",学Java的人最熟悉为什么有分布式
- github地址:https://github.com/vuejs/vuex 官网的话,在vue官网的生态中有vuex
- 什么时候用vuex?
- 1、多个组件依赖同一状态( 数据 )【 ps:两个帅哥,都想要同一个靓妹 】
- 2、不同组件的行为 需要 变更同一状态【 ps:洗脚城的两个妹子 都想要把 客户钱包中的money变到自己荷包中 】
- 3、上面的使用一句话概括:多组件需要分享数据时就使用Vuex
3.7.1、vuex原理
- 首先这个东西在官网中有,但是:不全
- 所以:把官网的原图改一下
- 上述内容只是对原理图有一个大概认识而已,接下来会通过代码逐步演示就懂了
3.7.2、搭建Vuex环境
1、在项目中安装vuex 指令:npm install vuex
2、编写store
3、让store在任意组件中都可以拿到
- 这样store就自然出现在其他组件身上了【 ps:利用了vc和vm的内置关系 ,验证自行验证,在其他组件中输出this就可以看到了】
vuex环境搭建小结
-
1、创建文件src/store/index.js
-
// 引入vuex import vuex from "vuex" // 使用vuex —— 需要vue 所以引入vue import Vue from "vue" Vue.use(vuex) // 创建store中的三个东西actions、mutations、state const actions = {} const mutations = {} const state = {} // 创建store ———— 和创建vue差不多的套路 export default new vuex.Store({ // 传入配置项 ———— store是actions,mutations,state三者的管理者,所以配置项就是它们 actions, // 完整写法 actions:actions ,是对象嘛,所以可以简写 mutations,state })
-
-
2、在main.js中配置store
-
import App from "./App.vue" import Vue from "vue" // 引入store import store from "./store" // 由于起的名字是index,所以只写./store即可,这样默认是找index,没有这个index才报错 const vm = new Vue({ render: h=>h(App), components: {App}, // 让store能够被任意组件看到 ———— 加入到vm配置项中【 ps:和全局事件总线很像 】 store, template: `<App></App>`, }).$mount('#app')
-
3.7.3、简单玩一下vuex的流程
- 对照原理图来看
1、先把要操作的数据 / 共享数据放到state中
2、在组件中使用dispatch这个API把key-value传给actions
- actions就是第一层处理【 ps:服务员嘛 】
2、actions接收key-value【 ps:要是有逻辑操作,也放在这里面,ajax也是 】
- 这里就是调用commit这个API把key-value传给mutation,这个mutation才是真正做事的人【 ps:后厨嘛 】
3、mutations接收key-value
4、查看开发者工具【 ps:简单了解,自行万一下 】
-
另外:vuejs devtools开发工具版本不一样,则:页面布局也不一样,但是:功能是一样的
当然:前面说过,直接在组件中调commit这个API从而去和mutations打交道,这种是可以的,适用于:不需要逻辑操作的过程,示例就自行完了
以上便是简单了解vuex,前面的例子看起来没什么用,但是vuex这个东西其实好用得很
3.7.4、认识getters配置项
- 这玩意儿就和data与computed的关系一样
3.7.5、四个map方法
3.7.5.1、mapState和mapGetters
1、改造源代码 —— 使用计算属性实现
- 但是:上面这种是我们自己去编写计算属性,从而做到的,而Vuex中已经提供了一个东西,来帮我们自动生成计算属性中的哪些东西,只需一句代码就搞定
2、使用mapState改造获取state中的数据,从而生成计算属性
- 1、引入mapState 代码:
import {mapState} from "vuex"
- 2、使用mapState【 ps:对象写法 】
- 3、数组写法【 ps:推荐用的一种 】
-
...mapState({sum:'sum'})
这里面的sum:'sum'
这两个是一样的,那么:一名多用
-
3、使用mapGetters把getters中的东西生成为计算属性
- 1、引入mapGetters 代码:
import {mapState,mapGetters} from "vuex"
- 2、使用mapGetters【 ps:和mapState一模一样 ,对象写法 】
- 3、数组写法
3.7.5.2、mapActions 和 mapMutations
- 会了mapState,那么其他的map方法也会了,差不多的,只是原理是调了不同的API罢了,当然:也会有注意点
- mapState 和 mapGetters是生成在computed中的,而mapActions和mapMutations是生成在methods中的
1、mapActions —— 调的API就是dispatch
-
使用mapActions改造
- 1、引入mapActions 代码:
import {mapActions} from 'vuex'
- 2、使用mapActions【 ps:对象写法 】 —— 准备调入坑中
-
原因:
-
原因:
-
3、数组写法 —— 一样的,函数名和actions中的名字一样【 ps:一名多用 】
- 1、引入mapActions 代码:
2、mapMutations —— 这个和mapActions一模一样,只是调用的API是commit
- 所以:此种方式不演示了,会了前面的三种中任意一种,也就会了这个
3.7.6、简单玩一下组件共享数据
1、在state中再加一点共享数据
2、新增Person组件
3、共享数据
- 在Count组件中获取person,在person中获取Count【 ps:此步不演示,会了前者后者就会了 】
操作如下:
3.8、路由器 router
3.8.1、认识路由和路由器
-
路由:就是一组key-value的映射关系 也就是route
- key就是网站中的那个路径
- value 就是function 或 component组件
- function 是因为后端路由( 后端调用函数 对该路径的请求做响应处理 )
- component 组件就不用多说了
- 路由器 router:就是专门用来管理路由的【 ps:理解的话,就参照生活中的那个路由器,它背后有很多插孔,然后可以链接到电视机,那个插孔就是key ,而链接的电视机就是value嘛 】
- 在vue中,router路由器是一个插件库,所以需要使用
npm install vue-router
来进行安装,这个东西就是专门用来做单页面网站应用的 - 所谓的单页面网站应用就是 SPA,即:只在一个页面中进行操作,路径地址发生改变即可,然后就把相应的东西展示到当前页面,不会发生新建标签页打开的情况【 参照美团网址,进行点一下,看看地址、页面更新、是否新建标签页打开、美团就是单页面网站应用 】
- SPA 整个应用只有
一个完整的页面
、点击页面中的导航链接不会刷新页面
,只会做页面的局部刷新
、数据需要通过ajax请求获取
- SPA 整个应用只有
3.12、路由器 router
3.12.1、认识路由和路由器
- 路由:就是一组key-value的映射关系 也就是route
- key就是网站中的那个路径
- value 就是function 或 component组件
- function 是因为后端路由( 后端调用函数 对该路径的请求做响应处理 )
- component 组件就不用多说了
- 路由器 router:就是专门用来管理路由的【 ps:理解的话,就参照生活中的那个路由器,它背后有很多插孔,然后可以链接到电视机,那个插孔就是key ,而链接的电视机就是value嘛 】
- 在vue中,router路由器是一个插件库,所以需要使用
npm install vue-router
来进行安装,这个东西就是专门用来做单页面网站应用的 - 所谓的单页面网站应用就是 SPA,即:只在一个页面中进行操作,路径地址发生改变即可,然后就把相应的东西展示到当前页面,不会发生新建标签页打开的情况【 参照美团网址,进行点一下,看看地址、页面更新、是否新建标签页打开、美团就是单页面网站应用 】
- SPA 整个应用只有
一个完整的页面
、点击页面中的导航链接不会刷新页面
,只会做页面的局部刷新
、数据需要通过ajax请求获取
- SPA 整个应用只有
3.12.2、简单使用路由器
1、准备工作:
- 1)、引入bootstrap.css
2、开始玩路由器 router
-
1)、给项目安装路由 指令:
npm install vue-router
-
2)、在main.js中引入并使用路由器【 ps:路由器是一个插件 】
-
3)、编写组件
-
4)、配置路由器【 ps:这也叫配置路由规则,就是key-value的形式,在前端中,key是路径,value是组件 】
-
5)、把配置的路由规则引入到main.js中
-
6)、在静态页面中使用路由【 ps:需要记住两个标签
<router-link ..... to = "路径名"></router-link>
和<router-view></router-view>
]-
<router-link ..... to = "路径名"></router-link>
是指:跳转 其中:路径名 就是 路由规则中配置的 path 参照a标签来理解 本质就是转成了a标签 -
<router-view></router-view>
是指:视图显示 就是告知路由器 路由规则中配置的component应该显示在什么位置,和slot插槽一样,占位 -
页面结构源码如下:
-
<template> <div> <div class="row"> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"> <h2>Vue Router Demo </h2></div> </div> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <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> <!--对照a标签 <a class="list-group-item active" href="./home.html">Home</a> --> </div> </div> <div class="col-xs-6"> <div class="paner"> <div class="paner-body"> <router-view></router-view> </div> </div> </div> </div> </div> </template> <script> export default { name: 'App', } </script>
-
-
-
7)、运行效果如下:
另外:从此处开始,可以先摸索Element-ui组件库了,这是专门搭配vue来做页面的网站 和 bootstrap一个性质,这个东西后续要用,网址如下:
vue-router使用小结
-
1、安装vue-router ,命令:
npm install vue-router
-
2、在main.js中 指令:
import VueRouter from "vue-router"
-
3、应用vue-router插件,指令:
Vue.use(VueRouter)
-
4、编写router路由规则
-
// 引入路由器 import VueRouter from "vue-router" // 引入需要进行跳转页面内容的组件 import About from "../components/About.vue" import Home from "../components/Home.vue" // 创建并暴露路由器 export default new VueRouter({ routes: [ // 路由器管理的就是很多路由 所以:routes 是一个数组 { // 数组里面每个路由都是一个对象 它有key和value两个配置项【 ps:还有其他的 】 path: '/about', // 就是key 也就是路径名,如:www.baidu.com/about这里的about component: About // 就是value 也就是组件 },{ path: '/home', component: Home }, ] })
-
-
5、实现切换( active-class 可配置高亮样式 )
-
<router-link class="list-group-item" active-class="active" to="./about">About</router-link>
-
-
6、指定展示位置
-
<router-view></router-view>
-
3.12.3、聊聊路由器的一些细节
- 1、路由组件以后都放到page文件夹下,而一般组件都放到components中
- 2、路由切换时,“隐藏”了的路由组件,默认是被销毁掉了,需要时再去重新挂载的【 ps:示例自行通过beforeDestroy和mounted进行测试 】
- 3、每个组件都有自己的
$route
属性,里面存储这自己的路由信息 - 4、整个应用只有一个router,可以通过组件的
$router
属性获取到【 ps:验证自行把不同组件的这个东西绑定到window对象上,然后等路由组件挂载完毕了,拿到它们进行比对,答案是:false 】
3.12.4、多级路由
1、在src/page下再新建两个路由组件
2、给home路由规则编写多级路由
3、重新编写Hmoe.vue路由组件
-
源码如下:
-
<template> <div> <h2>我是Home的内容</h2> <div> <ul class="nav nav-tabs"> <li> <!-- 多级路由,这里的to后面需要加上父级路径 先这么写,它可以简写,后续进行处理 --> <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/message">Message</router-link> </li> </ul> <ul> <router-view></router-view> </ul> </div> </div> </template> <script> export default { name: 'Home' } </script>
-
4、运行效果如下
3.12.5、路由传参
1、query的字符串传参写法【 ps:也就是路径传参嘛,适合传递少量参数 】
- 1)、编写数据,改造Message.vue路由组件【 ps:传递数据者 】
- 2)、编写Detail.vue路由组件【 ps:数据接收者 】
- 3)、编写路由规则
- 4)、效果如下:
2、query的对象写法 【 ps:适合传递大量参数 】
- 运行效果也是一样的
3.12.6、命名路由
- 这个东西就是为了处理path中的那个字符串很长的问题,相当于起个别名
实例:
- 1)、修改路由规则
- 2)、使用命名路由精简path
- 3)、运行效果如下
命名路由小结
-
作用:简化路由跳转时的path写法
-
使用:
-
给命令命名
-
{ path: '/home', component: Home, children: [ { path: 'news', component: News },{ path: 'message', component: Message, children: [ { path: 'detail', // 使用另一个配置项name 命名路由,从而让path更精简 name: 'detail', component: Detail } ] }, ] },
-
-
简化路由跳转写法
-
<!-- 简化前写法 --> <router-link :to="{ path: '/home/message/detail', query: { id: m.id, title: m.title } }"> {{m.title}} </router-link> <!-- 简化后写法 --> <router-link :to="{ name: 'detail', query: { id: m.id, title: m.title } }"> {{m.title}} </router-link>
-
-
3.12.7、路由另一种传参 - params
- 注意:这种传参必须基于name配置项才可以,待会儿会说明
- 另外就是:这种传参也就是后端中的RESTful传参
1、使用params传递参数【 ps:数据的传递者 】,这一步和以前的query传递没什么两样,只是改了一个名字而已
- 1)、字符串写法 / 对象写法
- 2)、修改路由规则
- 3)、获取参数 【 ps:和query相比,就是数据存放的位置变了一下而已,可以在mounted中输出this.$route看一下结构 】
- 4)、效果如下
路由params传参小结
-
1、配置路由
-
{ path: '/home', component: Home, children: [ { path: 'news', component: News },{ path: 'message', component: Message, children: [ { // 使用params传参,则:需要把path的规则改了,就是占位,接对应参数 path: 'detail/:id/:title', name: 'detail', // 对象写法,必须保证有这个配置项 component: Detail } ] }, ] },
-
-
2、传递参数
-
<!-- 使用params传递参数 --> <!-- 字符串写法 --> <router-link :to="`/home/message/detail/${m.id}/${m.title}`"> {{m.title}} </router-link> <!-- 对象写法 这种写法必须保证里面是name,而不是params,否则:页面内容会丢失的 --> <router-link :to="{ name: 'detail', params: { id: m.id, title: m.title } }"> {{m.title}} </router-link>
-
注意点:路由携带params参数时,若使用的to的对象写法,则:不能使用path配置项,必须用name配置项
-
-
3、接收参数
-
{{$route.params.id}} {{$route.params.title}}
-
3.12.8、路由的props配置项
- props这个东西在组件的父传子通信时见过,但是:不是一回事,那是组件间的,现在是路由的,写法几乎不一样
- 这里有一句话:哪个路由组件要接收数据,props就配置在哪个路由规则中
- 回到问题:插值语法讲究的就是简单的模板语法,而下图这种就要不得,所以:要简化
1、布尔值写法
-
为true时,则把path接收到的所有params参数以props的形式发给所需路由组件, 如:这里的Detail
- 注意:是params传递的参数,所以:这就是缺点之一
- 另外就是:以props形式传给所需组件,所以:在所需路由组件那边需要使用pros:['xxxx']来进行接收,从而在所需组件中使用数据时就可以进行简化了
2、函数写法
- 这种写法:是最灵活的一种,props为函数时,该函数返回的对象中每一组key-value都会通过props传给所需数据的路由组件【 ps:如这里的Detail 】
- 这种写法可以获取query传递的数据,也可以接收params传递的,注意点就是:在函数中调用时的名字变一下即可
- 这种的好处就是:一是query和params都可以接收,二是:把数据接收的逻辑写到要接收数据的路由组件的路由规则去了,逻辑更清晰,不至于到处找逻辑代码
3、另外的方式
- 另外的方式,不推荐使用,所以:简单了解即可
- 1)、在数据使用者处自行抽离代码,弄成计算属性 【 ps:如示例中的Detail路由组件,取数据时在插值语法中麻烦,那就在下方配置计算属性从而抽离代码,但是:画蛇添足,因为:在计算属性中拿数据时会使用
this.$route.queru / params.xxxx
代码量大的话,这不就还得多写N多this吗 - 2)、对象写法 —— 不用了解,知道有这么一个东西即可,需要时自行百度即可【 ps:这个东西接收数据是死的,开发中基本上用都不用 】
- 此种方式:是将该对象中所有的key-value的组合最终通过props传给所需路由组件
3.12.9、router-link中的replace属性
- 上面这种模式就是push模式,它的原理就是栈空间,压栈push嘛【 ps:router-link中的默认模式就是push模式 】
- 但是还有一种模式是:replace模式,这种模式是产生一个记录之后,就把上一次的记录给干掉了,所以效果就是不会有回退的记录,回退按钮都点不了【 ps:就是走一路短一路,没后路了 ^ _ ^ ,要实现这种模式也简单,就是:在router-link中加一个replace属性即可 】
- 这样之后,再点击Home / About时,它的历史记录不会留下
- 这样之后,再点击Home / About时,它的历史记录不会留下
router-link的replace属性小结
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式,分别为
push
和replace
,其中:push
是追加历史记录,replace
是替换当前记录,路由跳转时默认为push
- 开启replace模式的方式:
<router-link replace ......>News</router-link>
3.12.10、编程式路由导航
- 这玩意儿就是不再借助
router-link
来实现路由跳转,前面玩了$route
,而现在就是来玩的$router
- 先看一下
$router
这个东西,顺便知道掌握哪些API,就是下图中的五个
1、玩一下push和replace这两个API
- push和replace的原理就是前面说的,一个保留历史记录,一个会清除上一次的历史记录
2、玩一下back和forward这两个API
- 这两个就是浏览器中的前进和后退
3、玩一下go这个API
- go()中可以用正数和负数,正数表示:前进所填数字步;负数就是后退所填数字的绝对值步
3.12.11、缓存路由组件
- 前面不是说了:路由组件在被切换走之后,是会被销毁的,因此:这种情况就会导致有时某个路由组件中的数据不应该被销毁,而是保留起来,所以:就需要借助即将说明的知识点,就是使用了一个
<keep-alive include = "componentName"></keep-alive>
标签来实现
1、缓存一个路由组件 —— 字符串写法
- 方便验证,加上如下的代码
2、缓存多个路由组件 —— 数组写法
- 演示就不做了
3.12.12、另外的生命钩子
- 在基础篇中就说过:除了哪里讲的8个生命钩子【 ps:4对 】,其实还有三个生命钩子没有玩,也说了等到路由之后再整
1、另一对生命钩子
-
这一对生命钩子就是:activated 和 deactivated,其中:
- activated 就是激活 【 ps:我想见你了,就调它 】
- deactivated 就是失活 【 ps:我不想见你了,你离开吧,就调它,有点类似于beforeDestroy这个钩子,但是:处理的情况不一样 】
- 适用场景:提前使用了
keep-alive include = "xxx"
保留改组件,切换后不让其销毁【 ps:beforeDestroy不起作用了 】,那么:又想要最后关掉定时器之类的,就可以使用这两个钩子函数
-
实例:
2、另外一个钩子函数就是nextTick
- 这个东西不演示了,它是为了:让解析时产生时间差,因为:有些东西需要把解析完之后的样子插入到页面中了才可以继续做另外的事情,因此:就可以借助nextTick,从而:让一部分模板先被解析好,放入页面中,然后再解析后面的一些东西时执行一些我们想要的逻辑,如:input框,有这么一个场景,让input渲染到页面时,我鼠标的焦点就在input框中,这就可以使用此钩子函数,在挂载时调用此钩子,然后把光标聚焦的逻辑放到nextTick回调中
-
nextTick钩子的玩法官网有,一看就懂
3.12.13、路由守卫 - 重要、开发常用
- 所谓的路由守卫,就是权限问题,即:拥有什么权限,才可以访问什么路由
3.12.13.1、全局前置路由守卫
- **所谓的全局前置路由守卫,特点之一就体现在前置二字上,它的API是beforeEach( ( to, from, next ) => { } ),也就是:在路由切换之前回调会被调用 / 初始化渲染时回调会被调用 **
-
实例:
- 不方便演示,所以直接说,to就是切换路由组件之后的组件位置【 ps:即 去哪里,目标组件 】;而from 是 切换路由组件之前的组件位置 【 ps:即 来自哪里 从哪个路由组件来 】;另外 next是一个函数next(),就是放行的意思
-
现在做一个操作,在浏览器中缓存一个key-value,然后访问News、Message路由时判断key-value是否对得上,对就展示相应的路由组件,否则:不展示
-
玩点小动作
-
玩点小动作
-
当然:前面的过程有一个小技巧可以简化
-
给路由规则添加一个meta配置项,就是路由元数据,利用这个配置项,我们可以给路由中放一些我们想放的东西进去【 ps:哪个路由需要权限验证,就在哪个路由中加入meta配置项 】,那就改造吧
3.12.13.2、全局后置路由守卫
- 这玩意儿就和全局前置路由守卫反着的嘛,但是:处理场景不一样
- 全局后置路由守卫 调用的API是afterEach( ( to, from ) => { } ),注意:和全局前置路由守卫相比,少了next参数,后置了嘛,都已经在前面把权限判断完了,你还考虑放不放行干嘛,这个全局后置路由守卫做的事情其实不是去判断权限问题,而是收尾,做一些过了权限之后的判断问题,比如:点击某个路由组件之后,只要可以查看这个组件内容,那么:就把网页的页签标题给换了
- 这个API是 初始化渲染时回调会被调用 / 路由组件切换之后会被调用,
-
实例:
-
操练一手:【 ps: 先用纯的全局前置路由守卫来做 】
- 先加点东西进去
-
上面看起来成功了,但是有bug,可以试着把network中的网络调成slow 3G,可以稍微清楚地看到效果 ,想要改成功,就算把项目中 public/index.html的title改了,也是一样,会有加载过程,因此:想在全局前置路由守卫中达到想要的效果,改出花儿来也莫得办法,而且在全局前置路由守卫中写两遍一样的代码根本不标准
- 先加点东西进去
-
操练一手:【 ps: 先用纯的全局前置路由守卫来做 】
-
想要实现前面的效果,那就需要全局后置路由守卫登场了,掉一下API,里面一行代码搞定
- 效果就不演示了,已经达到效果了【 ps:注意得把public/index.html中的title改成'大数据智慧云生平台',不然访问根目录时也有加载过程,这不是此知识点的锅,因为:原生的public/index.html的title是读取的package.json中第二行的name 】
3.12.13.3、独享路由守卫
- 这玩意儿就是指:某一个路由独享的路由守卫,调用的API是:beforeEnter( ( to, from, next )=>{ } ),它是指:在进入配置这个API的路由之前回调会被调用,其中:to、from、next的意思和前面全局前置路由守卫一样,但注意:这种没有什么后置之类的,它只有这一个前置,即:独享路由守卫
- 最后:路由守卫之间,是可以随意搭配的
-
实例:
3.12.14、路由器的两种工作模式
-
这两种模式是:hash和history模式
-
hash模式 路由器的默认模式 就是路径中有一个#,这#后面的内容不用随http传给服务器,服务器也不会收到【 ps:前端玩一下而已 】,注意:这个hash和后端中的hash算法哪些东西不一样啊,别搞混了
- history模式 这个就好理解了嘛,就是没有了那个#,然后路径中ip:port之后的东西是会随着http传给服务器的
-
hash模式 路由器的默认模式 就是路径中有一个#,这#后面的内容不用随http传给服务器,服务器也不会收到【 ps:前端玩一下而已 】,注意:这个hash和后端中的hash算法哪些东西不一样啊,别搞混了
-
hash和history两种模式的区别:
- 1、hash模式 路径中有#,且#后的内容不会随http传给服务器;而history模式 路径中没有# ip:port之后的东西会随http传给服务器
- 2、hash模式的兼容性好,而history模式的兼容性略差
-
在路由器中hash和history两种模式的切换 在路由规则中加个全新的配置项mode即可
3.12.15、关于项目上线的问题
- 了解这个东西是因为前面说的hash和history的另一个区别,在上线时有个坑 / 注意点
1、编写完了程序之后打包项目
- 启动项目一直用的是
npm run serve
,在脚手架时就说过还有一个命令:npm run build
,那时说过:后端要的前面资源是HTML+CSS+JS,所以此时项目打包就需要用到它了 -
先把路由器的工作模式切换成history模式,然后再打包,这样方便演示bug
2、使用node+express框架编写一台小服务器模拟一下上线
-
自行新建一个文件夹,然后使用vscode打开
-
1)、让文件夹变成合法包 指令:
npm init
-
2)、安装express 指令:
npm install express
注意:这一步很容易因为自己当初配置nodejs时操作不当,导致权限不够啊,就会报一堆warn和error -
3)、新建一个js文件,编写内容如下
-
源码如下:
-
// 1、引入express 注意:这里就不是ES6的模块化了,而是commanjs模块化 const express = require('express') // 2、创建一个app服务实例对象 const app = express() // 3、端口号监听 app.listen(8001,(err)=>{ // err是一个错误对象 if( !err ) console.log("服务器启动成功"); }) // 4、配置一个后端路由 app.get('/person',(req,res)=>{ // req就是request res就是response res.send({ name: '紫邪情', age: 18 }) })
-
-
4)、启动服务器 指令:
node server
-
5)、访问服务器中的端口测试一下
3、准备工作弄完了,现在把刚刚使用npm run build
打包的dist中的文件复制到服务器中去
- 在服务器中新建一个static / public文件夹【 ps:这两个文件夹后端的人很熟悉了,就是SpringBoot中的那两个,这里就不解释了,这两个文件夹键哪一个都可以 】
4、让复制进去的文件能够被服务器认识
-
重新执行
node server
开始演示路由器的两种工作模式的另一个坑【 ps:别忘记有个权限认证啊,在缓存把对应东西放上,不然有些路由组件点不了 】-
整bug,随便点一些路由组件之后,刷新页面【 ps:只要保证路径不是localhost:8001即可,让它后面有点东西 】
- 这就是history模式的坑,但是:切换成hash就不会出现这样,出现上述的情况是因为:刚刚我们在页面中随便点路由组件都有页面是因为:那些都是静态页面嘛,那些数据啊、历史记录啊都是原本就有的,即:不走网络请求,但是:刷新之后,是走网络请求的,也就会把路径中ip:port之后的东西随着http发给服务器了,它去服务器找资源就是找
http://localhost:8001/home/news
中的/home/news,服务器中那有这个资源,所以:404呗 -
想要history模式也和hash一样,刷新不出错,就需要找后端人员进行处理,需要后端人员配合你这边拿过去的资源来做,后端处理这种问题的方式有很多,如:Nginx【 ps:但是嘛,有时别人甩你个锤子,最终代码出问题是自己背锅罢了 】,所以:自己解决,需要借助一个插件 connect-history-api-fallback
- 这就是history模式的坑,但是:切换成hash就不会出现这样,出现上述的情况是因为:刚刚我们在页面中随便点路由组件都有页面是因为:那些都是静态页面嘛,那些数据啊、历史记录啊都是原本就有的,即:不走网络请求,但是:刷新之后,是走网络请求的,也就会把路径中ip:port之后的东西随着http发给服务器了,它去服务器找资源就是找
-
整bug,随便点一些路由组件之后,刷新页面【 ps:只要保证路径不是localhost:8001即可,让它后面有点东西 】
3.13、结语
Vue2到此结束,另外还有一些Vue UI组件库
-
移动端
- Vant 链接:https://youzan.github.io/vant/#/zh-CN
- Cube UI 链接:https://didi.github.io/cube-ui/#/zh-CN
- Mint UI 链接:http://mint-ui.github.io/#!/zh-cn
-
PC端
- Element UI 链接:https://element.eleme.cn/#/zh-CN
- IView UI 链接:https://www.iviewui.com
接下来的技术点就是:Vue3