今天的内容有意思了,朋友们继续对我们之前的案例完善,是这样的我们之前是不是靠props来完成父给子,子给父之间传数据,其实父给子最好的方法就是props但是自给父就不是了,并且今天学下来,不仅如此,组件间任何层级的关系我都可以传数据了,兄弟之间,爷孙之间等等等等
七.浏览器本地存储
1.localStorage
关闭浏览器数据不会丢失
通过它上面的一个api可以存储数据 .setitem()两个参数,前面是键,后面是值,要注意的是都要以加引号以字符串形式进行存储,而且就算你不加引号,最终呈现效果也会强制给你调用toString这个方法
可以看到就算是字符串也变成了字符串类型,那么问题就来了,那如果我传入的是一个对象,也被强制调用了toString那我对象就没了,什么也看不到了
所以如果是要存储对象数据的时候需要将对象用转为json数据在进行存储,我们都知道json本质上还是一个字符串
剩下还有三个api:读取getitem()、删除removeitem()、清空clear()这个不用传数据
注意:读取没有的属性,将会显示null
2.sessionStorage
浏览器一关就没有数据了
他的api跟local一样
3.TodoList案例完善
我们刚才做的案例数据在一刷新,就会回到初始案例,显然这样是不行的,每个人都应该有属于自己的数据,就可以配合localstorage来,通过监视属性来做,监视我们的data数据,当一发生变动就让最新的值setitem到我们的本地存储
我在这里卡了半天啊,我一直在纠结,为什么这个local里面的属性名会给我变成个数组,最终查到了,原来watch监视属性里面的参数本身就是个数组,放的对象的形式,做到这一步当我们敲击回车,已经可以看到localstorage将我们的数据存储下来了,但是呢我们的页面还没有,这个时候需要将我们原来的数据,注意datas本身是一个数组这里就不用再用数组包裹了
这个时候我们我们刷新就能看到我们之前已经添加过的数据了
bug:当我们把local清空准备重新试一下,就会报错
不能够读取null的长度差不多是这个意思,哪里来的null?还记得前面说过当我们本地存储如果读取的是一个不存在的属性那他读取出来就是null,所以问题出现在这里,这个时候localstorage里面还没有这个属性的,主需要采用短路运算,让原始数据,存在就用它否则就为一个空数组
bug:虽然完成了刷新添加的数据不会丢失,但是我们的选择刷新了会丢失,为什么,问题出现在watch这里,我们对watch的监视只是看dataArr有没有整体改动,用了unshift,vue都能检测到,很明显这是一个能够改变自身的方法,但是我们去勾选前面的选择,却是深层次里面的了,所以监视的deep属性就来,只有监测到深层次的改动才又会重新setitem
八.自定义事件
- 方法一 : @或者v-on方法完成一个案例,当我们点击一个子组件,会把子组件的名字传到app里面来并打印出来,用到props的子传父方法,怎么用自定义事件来完成这件事情,首先在app的组件标签写上我们的自定义事件名,用v-on来绑定,
那么这个事件怎么来触发呢,这样来理解,当我们通过组件标签给她绑定了一个自定义事件,那么此时这个组件实例对象vc身上肯定已经有这个事件了,至于怎么来触发就需要回到我们的组件里面去定义
这里先把我们的触发后的事件处理函数定义好,然后关键逻辑 主要是在我们要触发的这个组件里面,想要这个自定义事件要怎么触发其实是听你自己的,同样还是需要在这边生命一个内置的事件,然后在这个事件函数里面用到一个api $emit,他的第一个参数就是你的自定义的事件的名字,第二个参数可以传参给你自定义事件的处理函数
props和自定义事件异同点:
共同:都可以实现子给父传数据,而且都是通过父组件里面的回调来实现的
不同点:props需要接收,而这种方法,直接拿来用都不用调用这个函数,props还需要自己去调用这个函数把参数传进去
-
方法二: 针对于上面的自定义事件的形式,我们还有第二种方法也叫做 ref法,通过在app给子组件添加一个ref,前面就说过我们用ref获取来的组件是这个组建的实例对象,有了实例对象,直接通过mounted这个生命周期钩子,为什么要在这里做,因为只有挂载完毕了我们才能拿到这个实例对象,vue才会new 组件构造函数,继续通过$on这个方法给这个实例对象在他身上添加一个自定义事件
做到这一步,我们组件里面的代码不变,还是click同时还是通过$emit 这个api来触发自定义事件,做到这一步意思就是,一切准备就绪,一旦在这个组件触发了这个自定义事件就会执行自定义事件的回调函数,那么回到函数呢?直接在后面以参数的形式添加
-
如果说我只想执行一次用v-on方法就是直接用事件修饰符,用ref方法,就调用$once这个api
注意如果是子组件多个参数传进来,可以使用这种方式将其以一个数组方式接受
8.1解绑自定义事件
-
解绑单个事件,写在子组件里面,全新api $off
-
解绑多个,用字符串包裹
-
解绑所有,this.$off()不传参数
回顾前面所说的一个生命周期的问题,说过当执行了destroy之后,身上的事件、监听器等都会被销毁,但是说的事件是自定义事件,和vue事件不包括原生js事件,比如你点击一个按钮n++,这个时候n肯定不加了,但是如果你写的有console.log那么这个log肯定还是你点一次输出一次的,自定义事件就更不用说了,也是vue实现的,所以也会被销毁实现不了了,还有一个点就是,vm被销毁了他下面的子组件的这些自定义事件等也会被销毁
8.2注意
-
是一种组件间的通信方式,适用于 子组件给父组件传
-
我们的组件标签上也可以绑定内置事件,但是需要 .native这个修饰符,绑定后比如一个click那就是这个子组件的最大的div可以触发也就是子组件的最外层
-
如果用下面这钟方式绑定自定义事件的时候,需要注意回调这里要么写在mtehods里面,要么写成一个箭头函数,不然this指向会变
8.3TodoList自定义事件
将之前这个案例子传父数据的地方都改成自定义事件,我就直接传几个,比如敲回车放入数组这里直接将原来的动态绑定改为自定义事件,简写形式
然后我们header这边的 props就可以删除了,同时在我们的键盘事件添加上调用api去触发自定义事件的命令
然后我们在用ref打标识的方法再做一次全选的方式,首先需要在app里面找到footer的子组件,给她打一个标识,然后在我们app这个组建的mounted函数里面来通过ref得到这个组件实例对象通过on这个api将自定义事件绑定上去,同时第二个参数为我们要调用这个事件的回调函数
然后后面基本是一样的,在我们子组件里面这个自定义事件该怎么触发,就写内置事件,写一个事件处理函数,然后在这个处理函数里面,emit这个api来触发自定义事件,同时将我们需要的参数传过来
九.全局事件总线
9.1安装全局事件总线
可实现任意组件间的通信
其实原理是这样的,我们左边这么多组件,要实现任意互相通信,可以由一个中间者实现,比如我们在B里面定义这个x给她通过$on绑定一个自定义事件,同时回调在我们B里面,注意是在B里面,所以当执行这个自定义事件的时候,是在B里面执行,完成了这一步,比如说我们要把A的数据传到B里面来,那么A就可以通过x这个对象用他身上的$emit这个api来触发我们的自定义事件同时把A身上的数据,传过来,这个时候我们B的回调里面就能收到这个数据了
其实原理跟我们前面的自定义事件很像,所以这么一看,这个x是不是要满足两个特性:一个是它能够被所有组件访问到,一个是他身上要有$on、$emit等api,第一个问题它能够被所有组件访问到,放在VueComponent上实现不了,因为我们说过每个VueComponent都是一个全新的构造函数,放在自己vc上更不可能,那就只有你能访问了,那么顺着那条线,是不是答案已经出来了,没错,要让所有组件都访问得到,那就只有Vue的原型对象了
第二个问题,怎么让他身上有这些api,这些api其实都是在vue原型对象上的,所以我们vm、vc都可访问得到,但是将它等于一个vc不太现实,因为我们是将它定义在入口文件的,那就只能为一个vm了,并且还要在vm的beforecreate这个生命周期函数来赋值,为什么要在生命周期函数里来赋值,因为这里讲究一个时间效应,太慢了不行的,前面也已经看过了ref打标识这种方法调用on这个api就是在mounted生命周期函数里面调用的,为什么因为讲mounted的时候就说过这里面是万物的开始,呱呱坠地的时候,什么定义定时器、自定义事件等等都是在这里,而且也只能在这里,不然时间晚了,我触发了你还没绑定好那怎么可以,所以说我们讲究一个时间效应,如果等到vm都定义完了,来一个x=vm,这个时候已经整个都挂载到页面上去了,我们组件里面的mounted也早已经执行完了,你的x都还没有这些api那就会报错,然后还只能写在beforecreate这个生命周期钩子,为什么,也不是说只能嘛,最好写在这里,created感觉也是可以的,因为这里数据挂载这些都还没做,但是也说过vue内置的一些事件、api之类的定义完了,我要这些就够了,我不需要数据
所以最终定义成这样就是最标准的了,而且我们一般x叫做$bus,因为这个东西的作用一是本身有点公交车的感觉,谁都能用,还有一个就是他的中文意思还有总线的意思
有一点必须要清楚,我想把A发送到B,那调用emit发射这个api的就是前者并且参数是数据,而后者就调用on并且参数为回调,你就拿到这个数据了,想干嘛干嘛了
然后就是,定义自定义事件也就是on肯定一挂载就要定义,所以写在mounted里面,而发送数据这边决定触发事件的方式
既然用到了出生mounted,最好也在临别beforeDestroy解绑我们的事件,一定要写事件名
9.2TODOList全局事件总线
又可以继续完善之前的案例了,其实父给子传数据,props是最好的方法,子给父传,用我们原始方法也行(props),自定义事件也好,都差不多的,这里最需要用到全局事件总线的,是孙给爷传,之前一直靠着list这个中间量props,他自己也没用只是给item带过去函数
定义好api
我们是item给app传,那就在item先emit,都是传过去id进行更新我们的数据里面的completed是否勾选和筛选进行删除操作
然后再mounted里面声明事件
十.消息订阅与发布
一种组件间的通信方式,适用于任意组件间通信
如果A需要C的数据,那么A就订阅消息,C就发布消息即可
原生JS无法轻松实现消息订阅与发布,需要第三方库(这里推荐 pubsub-js)
- 先安装这个库
- 哪里需要就在那里导入,我要把student的学生姓名传给school那他们两个都需要
,导入时注意 直接pubsub没有js
-
学生这边发布信息,参数为要传过去的数据,用到的api pubsub.publish,第一个参数为消息名,发布与订阅都需要的
-
学校这边订阅消息用到的是 pubsub.subscribe接受两个参数,第一个是我们的消息名字,所以一般取名为msgName,第二个参数才是我们传过来的数据,一般取名data,注意消息订阅同自定义事件、全局事件接收数据这边都是放在mounted函数里面的
-
注意一,同样需要在beforeDestory函数里面取消订阅,用到的api .unsubscribe,但是我们这里的消息订阅有点类似于定时器,需要一个变量去接收,这边取消订阅为这个变量名,注意不是消息名
像这样是拿不到的,都是两个作用域了,把它放到实例对象上去,作为一个属性即可
-
注意二:直接在订阅消息里面写这个函数同样要写为箭头函数,不然this会乱,或者也可以在methods里面写,这里直接调用