目录
前言
在没有学习兄弟组件的传值时,兄弟组件之间传值可以借助它们共同的父组件:把值传给它们的父组件,再按需要取值(子传父,父再传子)。这样做十分麻烦,本节将提供更简单的做法——事件总线。
注意,本节内容最好是在熟悉了父子组件的相互通讯的前提下再进行学习,如果不太熟悉可以参考下面两篇博客(第二篇文章原理讲解更多):Vue组件之间的数据共享详解
一,全局事件总线介绍
1.1 原理介绍
如图,组件结构图如下。现在我们有一个需求,想要将D组件中的数据以参数的形式传递给A组件。
怎么实现呢?利用以前的想法,我么可以让D组件先把数据传递给App组件,然后A组件再从App组件中获取数据。这个过程可以利用给组件设置自定义事件来实现。在这个过程中,App组件成了一个联络人。但是这样的方法,其实是绕了很大一个湾子去实现目的。
本节,我们将介绍一个很简单的方法:设置一个x,要求x也可以充当向上面app一样的联络人。如下图所示:同样是将信息从D传到A。我们可以在过程1中,在A中给x绑定一个自定义事件demo,在2过程中,x身上的demo事件被触发,x事件的回调在A中执行,参数成功传给了A。
1.2 x需要满足的条件
通过上述的讲解,可以看出x十分重要,那么,现在我们为来探讨x究竟是什么。
x的要求:x需要能够被所有组件看见(所有组件都能访问到x),x还需要能够使用on和emit。
我们可以推理一下,满足第二个条件,x可以是一个vc,也可以是vm。满足第一个条件的,也可以是一个vc或者是vm。
上面说了很多vc和vm,为了方便大家学习,在第二节会说清vc和vm的关系,以及对一些相关知识的复习。如果知识很熟可以跳过。
二,知识点的复习
2.1 vc是什么
vc的全称VueConpoment。vc的本质是一个构造函数。
而我们每写一个组件,组件的本质就是vc,也就是构造函数。
每用一个组件在App中,那么就相当于该组件多了一个组件的实例对象。
一个组件在做项目时会被复用多次,这些被复用的就是组件的实例对象:
2.2 vm管理vc如何体现
体现vm为何能管理vc,只需要看vm中是否有vc:
2.3 原型
这里要从构造函数聊起,在es6之前,没有类和对象,但是通过构造函数与构造函数的实例对象同样也可以实现面向对象编程。
在构造函数的相关知识中,构造函数的每一个实例对象都可以使用构造函数中的方法,但是有一个弊端:占用内存。如下图:
如何解决弊端:原型。构造函数中的原型:prototype,如果我们不把方法放在构造函数中,而是放在构造函数的原型对象上,依旧可以实现共享,并且不会占用内存。
原型中的方法是所有构造函数的实例对象共享的。也就是说,所有的实例对象都可以访问原型中的方法。
以上就是对原型知识的普及,如果觉得不够,可以查看博客:构造函数与原型对象,这里有对相关知识的详细讲解。
2.4 上述知识的串联
以上是我在学习这一块时复习的知识,以及引发的一些思考。为什么要讲解上述知识,我们回到对x的要求上。x需要能够被所有的组件看见,我们可以考虑把x放在prototype上;x需要能够使用on和emit,而组件可以,vm管理vc,所以vm也可以。因此,x可以是一个新的vm也可以是一个新的vc。这块知识可能有些晦涩,下面会结合实例理解。
三,实现需求
3.1 x的编写及讲解
如图所示,在main.js中定义一个vc并把它放到原型上去:
这里我来解释一下Demo是什么。Vue.extend()用来创建一个组件。组件本质是一个构造函数,由Vue.extend()自动生成。
如果向了解extend用法可以参考此博客:用原生的方式写vue组件之深度剖析组件内部的原理。
Demo是一个组件(构造函数),那么demo就是Demo的实例对象。Vue.prototype.x=demo, 是在Vue原型上创建了一个x。然后x是一个组件的实例对象。由于Vue的原型可以被所有组件实例对象访问,所以x可以被所有组件实例对象访问;而demo本质是一个组件的实例对象,所以可以使用on和emit,因此就达到了对x的两个要求。
3.2 使用x
那么,既然已经写好了x我们就可以来用到x了。如下图所示,这是要传递信息的一方,相当于刚说的A,在A中利用on给x定义一个自定义事件hello,并且后面有一个回调函数。如果对自定义方法不了解可以参考此博客:vue中利用ref实现更灵活的子向父传值中有涉及到。
既然已经绑定在了x上,我们可以在传值方使用emit调用此自定义方法:
这样一来就可以实现兄弟组件的数据传递。
四,标准写法
4.1 写法改动
标准写法的原理与上面一样。
标准写法同样是在main.js中,不同的是,x改为$bus,这个名字是为了适应vue的取名风格,其实什么名字都不重要,x也可以;还有就是,之前是创建了一个组件的实例对象,现在直接把vm拿出来,如下图所示:
这里需要解释几个问题:
1.使用beforeCreate:是因为,一般使用兄弟组件互传数据,最终的目的大多是为了使用数据,并呈现在页面上。而beforeCreate钩子时,数据监测数据代理都没有实现,且访问不到data的数据,所以数据要在还没有对数据进行处理的时候完整。
2.为什么用this:之前我们说可以用vm,但是如果在vm定义好后等于vm,页面已经更新完成,此时 $bus就没什么意义。如果在vm定义前等于vm,vm并未存在,无法令值为vm;所以利用钩子函数,后面赋值为this,是标准写法。
既然改动了$x,那么所有的 $x都要改为 $bus
学习了上面的原理,大家应该都对$bus有了了解,它叫事件总线,还叫事件车,组件之间的传递可以通过它来实现。
4.2 销毁
上文交代了事件车的原理,组件间数据的传递都可以用到它,但一般在使用中,一旦用完,为了加快项目运行的效率我们最好在beforeDestory钩子中解绑档期那组件所用到的事件,注意,只是销毁那个事件,而不是销毁所有。如下图所示,利用off即可。
五 关键代码
main.js
import Vue from 'vue'
import App from './compoments/App'
//写一个demo组件
// const Demo = Vue.extend({})//demo是组件的g构造函数
// const demo = new Demo()//组件的实例对象
// Vue.prototype.x = demo
new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this//标准写法,安装全局事件总线
}
})
App.vue
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
import School from './School.vue'
import Student from './Student.vue'
export default {
components: { School, Student },
name: 'App'
}
</script>
<style>
</style>
接受数据的一方:
<template>
<div>
<h2>学生姓名: {{ name }}</h2>
<h2>学生性别: {{ sex }}</h2>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
name: 'zxd',
sex: '女'
}
},
mounted() {
this.$bus.$on('hello', (data) => {
console.log(data)
})
},
}
</script>
<style>
</style>
传数据的一方:
<template>
<div>
<h2>学校名称: {{ name }}</h2>
<h2>学校地址: {{ address }}</h2>
<button @click="sendSchoolName">发送学校信息</button>
</div>
</template>
<script>
import School from './School.vue'
import Student from './Student.vue'
export default {
name: 'School',
data() {
return {
name: '尚硅谷',
address: '北京'
}
},
components: {
School, Student
},
methods: {
sendSchoolName() {
this.$bus.$emit('hello', this.name)
}
}
}
</script>
<style>
</style>
后记
以上是对事件车的原理讲解与写法说明,希望本节内容可以帮到读者朋友。有什么不对之处欢迎大家批评指正,同时也欢迎关注,后期会带来更认真的内容!