文章目录
组件通信总览
优点 | 缺点 | 应用场景 | ||
---|---|---|---|---|
1 | props-$emit | 1. 灵活简单,可以对props数据进行计算属性,数据监听等处理。 2. 父子组件通信灵活方便 | props篡改; 层级通信、兄弟组件通讯困难; | 嵌套不深的父子组件通信、关系不太复杂的兄弟组件组件通信 |
2 | this.$children &this.$parent | 简单,方便 | 1. this.$children 不可控性大,有一定风险;2. 不利于组件化; 3. 兄弟组件深层次嵌套组件通讯困难 | 更适合页面组件,而不适合一些第三方组件库,或者是公共组件。 |
3 | provide & inject | 1. 组件通信不受到子组件层级的影响; 2. 适用于插槽,嵌套插槽; | 1. 不适合兄弟通讯; 2. 父级组件无法主动通信 | 更适合深层次的复杂的父子代通信,子孙组件可以共享父组件的状态 |
4 | vuex | 1. 根本解决复杂组件的通信问题; 2. 支持异步组件通信 | 流程相比稍微复杂:vuex通信方式相比其他方式,比较复杂,而且如果不同的模块,需要建立独立的modules。 | 对于中大型项目,复杂的组件通信场景下,vuex是很不错的状态管理、数据通信方案 |
5 | 事件总线-eventBus | 1. 简单灵活,父子兄弟通信不受限制。 2. 通信方式不受框架影响 | 1. 维护困难,容易引起连锁问题 2. 需要谨小慎微的命令规范 3. 不利于组件化开发 | 更适合微信小程序、基于vue构建的小程序 |
6 | 事件总线-vueBus | 1. 简单灵活,任意组件之间通信。 2. 除了通信还可以使用watch 、computed等方法 | 基本与EventBus缺点一致 | 不考虑用vuex的中小型项目中,可以考虑采用vue事件总线这种通信方式,一定要注意命名空间,不要重复绑定事件名称。分清楚业务模块,避免后续维护困难 |
7 | $attrs & $listeners | 1. 解决props传递数据不能跨层的缺点; 2. 具有prop传参灵活简单的特点。 | 与props传参的缺点基本一致 | 解决使用在props的应用场景下的跨层通信问题 |
一 props-$emit
1. 用法
prop通信方式是最常见的、最常用的父子组件通信类型,我们可以直接在标签里面给子组件绑定属性和方法,对于属性我们可以直接通过子组件声明的prop拿到,对于父元素的方法,我们可以通过 this.$emit
触发。
父组件:将给子组件的数据fatherMes和提供给子组件的方法sonSay通过标签方式传递给子组件。
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<input v-model="mes" />
<button @click="send">对子组件说</button>
<div>子组件对我说:<span style="color:green">{{sonMes}}</span></div>
</div>
<son :fatherMes="sendSonMes" @sonSay="sonSayOfFar" />
</div>
</template>
<script>
import son from './Son'
export default {
name: 'father',
components: {
son
},
data() {
return {
mes: '',
sendSonMes: '', // 发送给子组件的信息
sonMes: '' // 来自子组件的信息
}
},
methods: {
// 传递给子组件
send() {
this.sendSonMes = this.mes
},
// 接受子组件信息
sonSayOfFar(value) {
this.sonMes = value
}
}
}
</script>
<style>
.far{
margin: 20px;
width: 400px;
height: 120px;
border: 2px solid blue;
}
</style>
子组件:通过props来定义接受父组件传递过来的信息;对于父组件传递的事件,通过this.$emit
来触发事件。
<template>
<div class="son">
<h3>我是子组件</h3>
<div> 父组件对我说:<span style="color:red">{{fatherMes}}</span></div>
<input v-model="mes" /> <button @click="send">对父组件说</button>
</div>
</template>
<script>
export default {
name: 'son',
props: {
fatherMes: {
type: String,
default: ''
}
},
data() {
return {
mes: ''
}
},
methods: {
send() {
this.$emit('sonSay', this.mes)
}
}
}
</script>
<style>
.son{
margin: 20px;
width: 400px;
height: 400px;
border: 2px solid blue;
}
</style>
区别react的props
这里和react的props有点区别,react组件更新来源于props更新和自身state改变,当props改变,会默认更新组件。而在Vue中,如果我们对父组件传过来的新的props没有做依赖收集(template模版收集,computed计算属性收集),组件是不会触动更新的。
效果
数据格式化:对props数据进行数据格式化,可以用computed接受props并将该数据格式化。
<template>
<div class="son">
<h3>我是子组件</h3>
<div> 父组件对我说:<span style="color:red">{{ computedFatherMes}}</span></div>
<input v-model="mes" /> <button @click="send">对父组件说</button>
</div>
</template>
<script>
export default {
name: 'son',
props: {
fatherMes: {
type: String,
default: ''
}
},
data() {
return {
mes: ''
}
},
computed: {
computedFatherMes() {
return this.fatherMes + 'haha,乖儿子'
}
},
methods: {
send() {
this.$emit('sonSay', this.mes)
}
}
}
</script>
<style>
.son{
margin: 20px;
width: 400px;
height: 400px;
border: 2px solid blue;
}
</style>
数据监听:使用watch函数监控props数据的改变,然后可以据此作出对应策略的操作。
watch:{
fatherMes(oldValue, newVaule) {
console.log(oldValue, newVaule)
}
},
2. 优点
props传递数据优点是显而易见的,灵活简单,可以对props数据进行计算属性,数据监听等处理。父子组件通信灵活方便。这里可能单单仅限父子一层。
3. 缺点
- props篡改:在子组件中使用父组件props的时候,如果涉及一些变量赋值、修改等操作,props被莫名其妙的修改了,此时父组件的数据也可能被篡改了。
- 如果props是基础数据类型,当在子组件直接修改props时,会报错。
- 如果props是引用数据类型,当在子组件可以修改props数据下某一个属性。
// 父组件
<template>
<div>
<div>父组件</div>
<son :fData="sonProps" />
</div>
</template>
<script>
import son from './sTampering'
export default {
name:'father',
components:{
son
},
data(){
return {
sonProps:''
}
},
}
</script>
// 子组件
<template>
<button @click="changeFatherProps">改变父组件props</button>
</template>
<script>
export default {
name:'son',
props:{
fData:{
required:true
}
},
methods:{
changFatherProps(){
this.fData = 'hello ,father'
}
}
}
</script>
当我们直接点击button 会抛出以下警。
但是当我们传过来的是一个引用数据类型,并且修改数据下某一个属性的时候。
// 父组件
data(){
return {
sonProps:{
a: 1,
b: 2
}
}
},
// 子组件
changFatherProps(){
this.fData.a = 'hello,world'
}
当点击按钮发现并没有报错,且打印父组件的sonprops数据,发现父组件的data数据已经被子组件篡改。
2 跨层级通信,兄弟组件通讯困难
对于父组件-子组件-子组件的子组件这种跨层级的通信,显然需要我们一层一层的prop绑定属性和方法,如果遇到更复杂的情况,实现起来比较困难。
对于兄弟组件之间的通讯,props需要通过父组件作为桥梁,实现子组件-> 父组件 -> 子组件通信模式,如果想要通过父组件做媒介,那么必定会造成父组件重新渲染,为了实现兄弟组件通信付出的代价比较大。
4. 应用场景
props的应用场景很简单,就是正常不是嵌套很深的父子组件通信,和关系不是很复杂的兄弟组件组件通信。
二 this.$children
&this.$parent
通过this下面的数据直接获取vue实例这种方法比较暴力,因为我们所谓的组件,最终都会是一个对象,存放组件的各种信息,组件和组件通过this.$children
和this.$parent
指针关联起来。因为在项目中只有一个root根组件,理论上,我们可以找到通过this.$children
this.$parent
来访问页面上的任何一个组件 ,但是实际上如何精确匹配到目标组件,确是一个无比棘手的问题。
1 用法
父组件
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<input v-model="mes" />
<button @click="send">对子组件说</button>
<div>子组件对我说:<span style="color:green">{{sonMes}}</span></div>
</div>
<son2 v-if="true" />
<son />
</div>
</template>
<script>
import son from './Son'
import son2 from './son2'
export default {
name: 'father',
components: {
son,
son2
},
data() {
return {
mes: '',
// 来自子组件的信息
sendSonMes: '',
sonMes: '' // 发送给子组件的信息
}
},
methods: {
/* 传递给子组件 */
send() {
/* 因为son组件是第一个有效组件所以直接去下标为0的组件 */
const currentChildren = this.$children[0]
currentChildren.accept(this.mes)
},
accept(value) {
this.sonMes = value
}
// /* 接受子组件信息 */
// sonSayOfFar(value) {
// this.sonMes = value
// }
}
}
</script>
<style>
.far{
margin: 20px;
width: 400px;
height: 120px;
border: 2px solid blue;
}
</style>
子组件
<template>
<div class="son">
<h3>我是子组件2</h3>
<div> 父组件对我说:<span style="color:red">{{ fatherMes}}</span></div>
<input v-model="mes" /> <button @click="send">对父组件说</button>
</div>
</template>
<script>
export default {
name: 'son',
data() {
return {
mes: '',
fatherMes: ''
}
},
methods: {
// 接受父组件内容
accept(value) {
this.fatherMes = value
},
// 向父组件发送信息
send() {
this.$parent.accept(this.mes)
}
}
}
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 400px;
border: 2px solid blue;
}
</style>
效果
我们可以清楚看到,和props通信相比,this.$parent
,this.$children
显得更加简洁,无需再给子组件绑定事件和属性,只需要在父组件和子组件声明发送和接受数据的方法。就可以实现组件间的通信,看起来很是便捷,但是实际操作中会有很大的弊端,而且vue本身也不提倡这种通信方式。而且这种通信方式也有很多风险性,我们稍后会给予解释。
2 优点
简单,方便 this.$children
,this.$parent
this.$refs
这种通信方式,更加的简单直接获取vue实例,对vue实例下的数据和方法直接获取或者引用。
3 缺点
1 this.$children
不可控性大,有一定风险。
如上的例子,我们稍微对父组件加以改动,就会直接报错。
之前的<son2 v-if="false" />
改成 <son2 v-if="true" />
,如果son2没有定义对应的方法,则其就会报错。
错误的原因很简单,我们用 $children
的下标获取,但是兄弟组件son2 v-if = true
之后,我们通过this.$children[0]
获取的就是son2
组件,而son2没有绑定方法,所以报错。
所以得出结论,对于v-if
动态控制组件显示隐藏的不建议用this.$children
用法,取而代之的我们可以用ref获取对应子组件的实例。
如上改成
<son ref="son" />
然后获取:
const currentChildren = this.$refs.son
根本解决了问题。
2 不利于组件化
直接获取组件实例这种方式,在一定程度上妨碍了组件化开发,组件化开发的过程中,那些方法提供给外部,那些方法是内部使用,在没有提前商量的情况下,父子组件状态不透明的情况下,一切都是未知的,所以不同开发人员在获取组件下的方法时候,存在风险,提供的组件方法,属性是否有一些内部耦合。组件开发思路初衷,也不是由组件外部来对内部作出一定改变,而往往是内部的改变,通知外部绑定的方法事件。反过来如果是子组件内部,主动向父组件传递一些信息,也不能确定父组件是否存在。
3 兄弟组件深层次嵌套组件通讯困难
和props方式一下,如果是兄弟直接组件的通信,需要通过父组件作为中间通讯的桥梁,而深层次的组件通讯,虽然不需要像props通讯那样逐层绑定,但是有一点,需要逐渐向上层或下层获取目标实例,如何能精准获取这是一个非常头疼的问题,而且每当深入一层,风险性和不确定性会逐级扩大。
4 应用场景
直接通过实例获取的通信方式适合已知的,固定化的页面结构,这种通讯方式,要求父子组件高度透明化,知己知彼,很明确父子组件有那些方法属性,都是用来干什么。所以说这种方式更适合页面组件,而不适合一些第三方组件库,或者是公共组件。
三 provide & inject
如果说vue中 provide 和 inject,我会首先联想到react的context上下文,两个作用在一定程度上可以说非常相似,在父组件上通过provide将方法,属性,或者是自身实例暴露出去,子孙组件,插槽组件,甚至是子孙组件的插槽组件,通过inject把父辈provide引进来。
1 用法
普通方式
我们用父组件 -> 子组件 -> 孙组件 的案例
父组件:通过provide把本身暴露出去,声明的名称要与子组件引进的名称保持一致
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<div>子组件对我说:<span style="color:green">{{sonMes}}</span></div>
<div>孙组件对我说:<span style="color:orange">{{grandSonMes}}</span></div>
</div>
<son />
</div>
</template>
<script>
import son from './Son'
export default {
name: 'father',
components: {
son
},
provide() {
return {
father: this
}
},
data() {
return {
grandSonMes: '',
sonMes: '' // 发送给子组件的信息
}
},
methods: {
grandSonSay(value) {
this.grandSonMes = value
},
// 接受子组件信息
sonSay(value) {
this.sonMes = value
}
}
}
</script>
<style>
.far{
margin: 20px;
width: 400px;
height: 140px;
border: 2px solid blue;
}
</style>
子组件:子组件通过inject把父组件实例引进来,通过this.father
可以直接获取到父组件,并调用下面的sonSay
方法。
<template>
<div class="son">
<h3>我是子组件</h3>
<input v-model="mes" /> <button @click="send">对父组件说</button>
<grandSon />
</div>
</template>
<script>
import grandSon from './grandSon'
export default {
name: 'son',
components: {
grandSon
},
data() {
return {
mes: ''
}
},
inject: ['father'],
methods: {
// 向父组件发送信息
send() {
this.father.sonSay(this.mes)
}
}
}
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 340px;
border: 2px solid blue;
}
</style>
孙组件:子组件通过inject把爷组件实例引进来,通过this.father
可以直接获取到爷组件,并调用下面的grandSonSay
方法。
<template>
<div class="grandSon">
<h3>我是孙组件</h3>
<input v-model="mes" /> <button @click="send">对页组件说</button>
</div>
</template>
<script>
export default {
name: 'grandSon',
data() {
return {
mes: ''
}
},
// 引入爷爷组件
inject: ['father'],
methods: {
// 向爷组件发送信息
send() {
this.father.grandSonSay(this.mes)
}
}
}
</script>
<style>
.grandSon {
margin: 20px;
width: 350px;
height: 200px;
border: 2px solid yellow;
}
</style>
效果
插槽方式:provide & inject
同样可以应用在插槽上。
父组件
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<div>子组件对我说:<span style="color:green">{{sonMes}}</span></div>
<div>孙组件对我说:<span style="color:orange">{{grandSonMes}}</span></div>
</div>
<slot-son>
<grand-son />
</slot-son>
</div>
</template>
<script>
import slotSon from './slotSon'
import grandSon from './grandSon'
export default {
name: 'father',
components: {
slotSon,
grandSon
},
provide() {
return {
father: this
}
},
data() {
return {
grandSonMes: '',
sonMes: '' // 发送给子组件的信息
}
},
methods: {
grandSonSay(value) {
this.grandSonMes = value
},
// 接受子组件信息
sonSay(value) {
this.sonMes = value
}
}
}
</script>
<style>
.far{
margin: 20px;
width: 400px;
height: 140px;
border: 2px solid blue;
}
</style>
子组件
// slotSon
<template>
<div class="son">
<h3>我是slot子组件</h3>
<input v-model="mes" /> <button @click="send">对父组件说</button>
<slot />
</div>
</template>
<script>
import grandSon from './grandSon'
export default {
name: 'son',
data() {
return {
mes: ''
}
},
inject: ['father'],
methods: {
// 向父组件发送信息
send() {
this.father.sonSay(this.mes)
}
}
}
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 340px;
border: 2px solid blue;
}
</style>
通信效果相同:
实际这种插槽模式,所在都在父组件注册的组件,最后孙组件也会绑定到子组件的children下面。和上述的情况差不多。
provied其他用法:provide不仅能把整个父组件全部暴露出去,也能根据需要只暴露一部分(一些父组件的属性或者是父组件的方法),上述的例子中,在子孙组件中,只用到了父组件的方法,所以我们可以只提供两个通信方法。但是这里注意的是,如果我们向外提供了方法,如果方法里面有操作this行为,需要绑定this
父组件
provide(){
return {
/* 将通信方法暴露给子孙组件(注意绑定this) */
grandSonSay: this.grandSonSay.bind(this),
sonSay: this.sonSay.bind(this)
}
}
子组件
/* 引入父组件方法 */
inject:['sonSay'],
methods:{
send(){
this.sonSay(this.mes)
}
},
孙组件
/* 引入爷组件方法 */
inject:['grandSonSay'],
methods:{
send(){
this.grandSonSay(this.mes)
}
},
2 优点
- 组件通信不受到子组件层级的影响
provide inject用法 和 react.context非常相似, provide相当于Context.Provider ,inject 相当于 Context.Consumer,让父组件通信不受到组件深层次子孙组件的影响。 - 适用于插槽,嵌套插槽
provide inject 让插槽嵌套的父子组件通信变得简单,这就是刚开始我们说的,为什么 el-form 和 el-form-item能够协调管理表单的状态一样。在element源码中 el-form 就是将this本身provide出去的。
3 缺点
- 不适合兄弟通讯
provide-inject
协调作用就是获取父级组件们提供的状态,方法,属性等,流向一直都是由父到子,provide提供内容不可能被兄弟组件获取到的,所以兄弟组件的通信不肯能靠这种方式来完成。 - 父级组件无法主动通信
provide-inject
更像父亲挣钱给儿子花一样,儿子可以从父亲这里拿到提供的条件,但是父亲却无法向儿子索取任何东西。正如这个比方,父组件对子组件的状态一无所知。也不能主动向子组件发起通信。
4 应用场景
provide-inject
这种通信方式,更适合深层次的复杂的父子代通信,子孙组件可以共享父组件的状态。
四 vuex
vuex算是vue中处理复杂的组件通信的最佳方案。
1 用法
vuex文件
// ./src/store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
fatherMes: '',
sonMes: '',
fatherMesAsync: ''
},
mutations: {
sayFather(state, value) {
state.fatherMes = value
},
saySon(state, value) {
state.sonMes = value
},
sayAsyncFather(state, value) {
state.fatherMesAsync = value
}
},
actions: {
asyncSayFather({ commit }, payload) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(payload)
}, 2000)
}).then(res => {
commit('sayAsyncFather', res)
})
}
}
})
在store文件中,我们声明三个mutations 分别是向父组件通信saySon,父组件向子组件通信,同步方法sayFaher和异步方法sayAsyncFather ,actions中模拟了一个三秒后执行的异步任务asyncSayFather。
main.js:main.js注入store
import store from './store/'
new Vue({
render: h => h(App),
store
}).$mount('#app')
父组件:父组件分别触发同步异步方法,把信息发送给子组件。用computed来接受vuex中的state。
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<input v-model="mes" />
<button @click="send">同步:对子组件说</button>
<br/>
<input v-model="asyncMes" />
<button @click="asyncSend">异步:对子组件说</button>
<br/>
<div>子组件对我说:<span style="color:orange">{{sonMes}}</span></div>
</div>
<son>
</son>
</div>
</template>
<script>
import son from './Son'
export default {
name: 'father',
components: {
son
},
data() {
return {
mes: '',
sayncMes: '' // 发送给子组件的信息
}
},
computed: {
sonMes() {
return this.$store.state.sonMes
}
},
mounted() {
console.log(this.$store)
},
methods: {
// 触发mutations,传递数据给子组件
send() {
this.$store.commit('sayFather', this.mes)
},
// 触发actions,传递数据给子组件
asyncSend() {
this.$store.dispatch('asyncSayFather', this.asyncMes)
}
}
}
</script>
<style>
.far{
margin: 20px;
width: 400px;
height: 140px;
border: 2px solid blue;
}
</style>
子组件:子组件的方法和父组件保持一致
<template>
<div class="son">
<h3>我是子组件</h3>
<div> 父组件对我说:<span style="color:orange">{{ fatherMes }}</span></div>
<div> 父组件对我说(异步):<span style="color:green">{{ fatherMesAsync }} </span></div>
<input v-model="mes" /> <button @click="send">对父组件说</button>
</div>
</template>
<script>
export default {
name: 'son',
data() {
return {
mes: ''
}
},
computed: {
// 接受父组件同步消息
fatherMes() {
return this.$store.state.fatherMes
},
// 接受父组件异步消息
fatherMesAsync() {
return this.$store.state.fatherMesAsync
}
},
methods: {
// 向父组件发送信息
send() {
this.$store.commit('saySon', this.mes)
}
}
}\
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 200px;
border: 2px solid blue;
}
</style>
效果:
2 优点
1 根本解决复杂组件的通信问题
vuex在一定程度上根本解决了vue复杂的组件通信情况,我们不再关心两个毫无干系的两个组件的通信问题。
2 支持异步组件通信
vuex中actions允许我们做一些异步操作,然后通过commit可以把数据传入对应的mutation,至于actions为什么可以执行异步,是因为里面底层通过Promise.resolve能够获取异步任务完成的状态。
3 缺点
流程相比稍微复杂
vuex通信方式相比其他方式,比较复杂,而且如果不同的模块,需要建立独立的modules。
4 应用场景
实际开发场景中,不会存在demo项目这样简单的通信,vuex的出现,就是解决这些比较复杂的组件通信场景。对于中大型项目,vuex是很不错的状态管理,数据通信方案。
五 事件总线
事件总线——EventBus
EventBus事件总线, EventBus 所有事件统一调度,有一个统一管理事件中心,一个组件绑定事件,另一个组件触发事件,所有的组件通信不再收到父子组件的限制,那个页面需要数据,就绑定事件,然后由数据提供者触发对应的事件来提供数据,这种通讯场景不仅仅应用在vue,而且也应用在react。
EventBus 核心思想是事件的绑定和触发,这一点和vue中 this.
e
m
i
t
和
t
h
i
s
.
emit 和 this.
emit和this.on一样,这个也是整个EventBus核心思想。接下来我们来重点解析这个流程。
1 用法
EventBus
class EventBus {
es = {}
/* 绑定事件 */
on(eventName, cb) {
if (!this.es[eventName]) {
this.es[eventName] = []
}
this.es[eventName].push({
cb
})
}
/* 触发事件 */
emit(eventName, ...params) {
const listeners = this.es[eventName] || []
let l = listeners.length
for (let i = 0; i < l; i++) {
const { cb } = listeners[i]
cb.apply(this, params)
}
}
}
export default new EventBus()
这个就是一个简单的事件总线,有on,emit两个方法。
父组件
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<input v-model="mes" />
<button @click="send" >对子组件说</button>
<div>子组件对我说:<span style="color:orange">{{sonMes}}</span></div>
</div>
<son />
<brotherSon />
</div>
</template>
<script>
import son from './son'
import brotherSon from './brother'
import EventBus from '../utils./eventBus'
export default {
name:'father',
components:{
son ,/* 子组件 */
brotherSon, /* 子组件 */
},
data(){
return {
mes:'',
sonMes:''/* 发送给子组件的信息 */
}
},
mounted(){
/* 绑定事件 */
EventBus.on('sonSay',this.sonSay)
},
methods:{
/* 传递给子组件 */
send(){
EventBus.emit('fatherSay',this.mes)
},
/* 接受子组件信息 */
sonSay(value){
this.sonMes = value
},
},
}
</script>
我们在初始化的时候通过EventBus的on方法绑定sonSay方法供给给子组件使用。向子组件传递信息的时候,通过emit触发子组件的绑定方法,实现了父子通信。接下来我们看一下子组件。
子组件
<template>
<div class="son">
<h3>我是子组件</h3>
<div> 父组件对我说:<span style="color:orange">{{ fatherMes }}</span></div>
<input v-model="mes" /> <button @click="send">对父组件说</button>
<br />
<input v-model="brotherMes" />
<button @click="sendBrother" >对兄弟组件说</button>
</div>
</template>
<script>
import EventBus from '../utils/eventBus'
export default {
name: 'son',
data() {
return {
mes: '',
brotherMes: '',
fatherMes: ''
}
},
mounted() {
// 绑定事件
EventBus.on('fatherSay', this.fatherSay)
},
methods: {
// 向父组件发送信息
send() {
EventBus.emit('sonSay', this.mes)
},
// 向兄弟组件传递信息
sendBrother() {
EventBus.emit('brotherSay', this.brotherMes)
},
// 父组件对我说
fatherSay(value) {
this.fatherMes = value
}
}
}
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 200px;
border: 2px solid blue;
}
</style>
和父组件的逻辑差不多,把需要接受数据的方法,通过EventBus绑定,通过触发eventBus方法,来向外部传递信息。我们还模拟了兄弟之间通信的场景。我们建立一个兄弟组件。
兄弟组件
<template>
<div class="son">
<h3>我是兄弟组件</h3>
<div> 兄弟组件对我说:<span style="color:orange"> {{ brotherMes }} </span></div>
</div>
</template>
<script>
import EventBus from '../utils/eventBus'
export default {
name: 'brother',
data() {
return {
brotherMes: ''
}
},
mounted() {
// 绑定事件
EventBus.on('brotherSay', this.brotherSay)
},
methods: {
// // 向父组件发送信息
// send() {
// EventBus.emit('sonSay', this.mes)
// },
// // 向兄弟组件传递信息
// sendBrother() {
// EventBus.emit('brotherSay', this.brotherMes)
// },
// // 父组件对我说
brotherSay(value) {
this.brotherMes = value
}
}
}
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 200px;
border: 2px solid blue;
}
</style>
我们可以看到,兄弟组件处理逻辑和父子之间没什么区别。
效果:
2 优点
1 简单灵活,父子兄弟通信不受限制。
eventBus的通信方式,相比之前的几种比较简单,而且不受到组件层级的影响,可以实现任意两个组件的通信。需要数据就通过on绑定,传递数据就emit触发。
2 通信方式不受框架影响
eventBus的通信方式,不只是vue可以用,react,小程序都可以使用这种通信方式,而且笔者感觉这种通信方式更适合小程序通信,至于为什么稍后会一一道来。
3 缺点
1 维护困难,容易引起连锁问题
如果我们采用事件总线这种通信模式,因为所有事件是高度集中,统一管理的,中间如果有一个环节出现错误,就会造成牵一发动全身的灾难.而且后期维护也是十分困难的。
2 需要谨小慎微的命令规范
现实的应用场景,要比demo场景复杂的多,实际场景会有无数对父子组件,无数对兄弟组件,我们不肯能每个事件都叫相同名字,所以eventBus绑定事件的命名要有严格的规范,不能起重复名字,也不能用错名字。
3 不利于组件化开发
eventBus通信方式是无法进行有效的组件化开发的,假设一个场景,一个页面上有多个公共组件,我们只要向其中的一个传递数据,但是每个公共组件都绑定了数据接受的方法。我们怎么样做到把数据传递给需要的组件呢?
4 应用场景
实现总线这种方式更适合,微信小程序,和基于vue构建的小程序,至于为什么呢,因为我们都知道小程序采用双线程模型(渲染层+逻辑层)(如下图所示),渲染层作用就是小程序wxml渲染到我们的视线中,而逻辑层就是我们写的代码逻辑,在性能上,我们要知道在渲染层浪费的性能要远大于逻辑层的代码执行性能开销,如果我们在小程序里采用通过props等传递方式,属性是绑定在小程序标签里面的,所以势必要重新渲染视图层。如果页面结构复杂,可能会造成卡顿等情况,所以我们通过eventBus可以绕过渲染层,直接有逻辑层讲数据进行推送,节约了性能的开销。
事件总线——new Vue
new Vue 这种通信方式和eventBus大致差不多,有一点不同的是,以vue实例作为eventBus中心,除了我们可以用 o n , on, on,emit之外,我们还可以用vue下的data,watch等方法,而且我们建立多个多个vue,作为不同模块的数据通信桥梁,相比上边那个EventBus方法,new Vue这种方法更高效,更适合vue项目场景。我们接着往下看。
1 用法
VueBus
// vueBus.js
import Vue from 'vue'
export default new Vue()
父组件
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<input v-model="mes" />
<button @click="send" >对子组件说</button>
<div>子组件对我说:<span style="color:orange">{{sonMes}}</span></div>
</div>
<son />
</div>
</template>
<script>
import son from './Son'
import VueBus from '../utils/vueBus'
export default {
name: 'father',
components: {
son
},
data() {
return {
mes: '',
sonMes: '' // 发送给子组件的信息
}
},
created() {
// 绑定属性
VueBus._data.mes = 'hello,world'
},
mounted() {
// 绑定事件
VueBus.$on('sonSay', this.sonSay)
},
methods: {
// 传递给子组件
send() {
VueBus.$emit('fatherSay', this.mes)
},
// 接受子组件信息
sonSay(value) {
this.sonMes = value
}
}
}
</script>
<style>
.far{
margin: 20px;
width: 400px;
height: 140px;
border: 2px solid blue;
}
</style>
我们通过 $on绑定了接受数据的方法,初始化的时候向 vue_data下面绑定了数据。
子组件
<template>
<div class="son">
<h3>我是子组件</h3>
<div> 父组件对我说:<span style="color:orange">{{fatherMes}}</span></div>
<input v-model="mes" /> <button @click="send">对父组件说</button>
<br />
<button @click="getFatherMes" >获取数据</button>
</div>
</template>
<script>
import VueBus from '../utils/vueBus'
export default {
name: 'son',
data() {
return {
mes: '',
fatherMes: ''
}
},
mounted() {
// 绑定事件
VueBus.$on('fatherSay', this.fatherSay)
},
methods: {
// 向父组件发送信息
send() {
VueBus.$emit('sonSay', this.mes)
},
// 父组件对我说
fatherSay(value) {
this.fatherMes = value
},
// 获取父组件存入vue中的数据
getFatherMes() {
console.log(VueBus._data.mes)
console.log(VueBus)
}
}
}
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 200px;
border: 2px solid blue;
}
</style>
和eventBus事件总线一样,我们还可以直接通过_data数据直接获取到父组件传递的内容。
效果
2 优点
1 简单灵活,任意组件之间通信。
和上边eventBus通信方式一样,这种通信方式很灵活,可以轻松在任意组件间实现通信。
2 除了通信还可以使用watch , computed等方法
如果我们通过vue作为通信媒介,那么只用其中的
e
m
i
t
和
emit和
emit和on真的是有点大材小用了,既然实例了一个vue,我们可以轻松的使用vue的 $watch computed等功能。
3 缺点
基本上EventBus的缺点,都在vue这种通信方式中都有存在。
4 应用场景
在项目中不考虑用vuex的中小型项目中,可以考虑采用vue事件总线这种通信方式,在使用的这种方式的时候,我们一定要注意命名空间,不要重复绑定事件名称。分清楚业务模块,避免后续维护困难。
六、$attrs
& $listeners
1. 用法
解决跨层通信问题。
$attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。(父传孙专用)
$listener
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。(孙传父专用)
父组件
<template>
<div class="father">
<div class="far">
<h3>我是父组件</h3>
<input v-model="mes" /><button @click="send1" >对子组件说</button>
<input v-model="gmes" /><button @click="send2" >对孙组件说</button>
<div>子组件对我说:<span style="color:orange">{{sonMes}}</span></div>
<div>孙组件对我说:<span style="color:orange">{{grandSonMes}}</span></div>
</div>
<son :fatherMes="ftoSon" @sonSay="sonSay"
:grandPaMes="ftoGrandSon" @grandSonSay="grandSonSay">
</son>
</div>
</template>
<script>
import son from './Son'
export default {
name: 'father',
components: {
son
},
data() {
return {
mes: '',
gmes: '',
sonMes: '',
grandSonMes: '',
ftoSon: '', // 发送给子组件的信息
ftoGrandSon: '' // 发送给孙组件的信息
}
},
methods: {
// 传递给子组件
send1() {
this.ftoSon = this.mes
},
// 传递给孙组件
send2() {
this.ftoGrandSon = this.gmes
},
// 接受子组件信息
sonSay(value) {
this.sonMes = value
},
// 接受孙组件信息
grandSonSay(value) {
this.grandSonMes = value
}
}
}
</script>
<style>
.far{
margin: 20px;
width: 400px;
height: 160px;
border: 2px solid blue;
}
</style>
子组件:为孙组件绑定$attrs
和$listeners
<template>
<div class="son">
<h3>我是子组件</h3>
<div> 父组件对我说:<span style="color:orange">{{fatherMes}}</span></div>
<input v-model="mes" /> <button @click="send">对父组件说</button>
<br />
<!-- 声明父组件需要传给孙组件的事件 -->
<grandSon v-bind="$attrs" v-on="$listeners" />
</div>
</template>
<script>
import grandSon from './grandSon'
export default {
name: 'son',
components: {
grandSon
},
props: {
fatherMes: {
type: String,
default: ''
}
},
data() {
return {
mes: ''
}
},
mounted() {
// 绑定事件
},
methods: {
// 向父组件发送信息
send() {
this.$emit('sonSay', this.mes)
}
}
}
</script>
<style>
.son {
margin: 20px;
width: 400px;
height: 350px;
border: 2px solid blue;
}
</style>
孙组件
<template>
<div class="grandSon">
<h3>我是孙组件</h3>
<input v-model="mes" /> <button @click="send">对爷组件说</button>
<div>爷组件对我说:<span style="color:orange">{{grandPaMes}}</span></div>
</div>
</template>
<script>
export default {
name: 'grandSon',
props: {
grandPaMes: {
type: String,
default: ''
}
},
data() {
return {
mes: ''
}
},
methods: {
// 向爷组件发送信息
send() {
this.$emit('grandSonSay', this.mes)
}
}
}
</script>
<style>
.grandSon {
margin: 20px;
width: 350px;
height: 200px;
border: 2px solid yellow;
}
</style>
2. 优点
$attrs
和$listeners
的优点主要时解决了props传递数据不能跨层的缺点,其也具有prop传参 灵活简单,的特点。
3. 缺点
当然了,$attrs
和$listeners
也需要与prop结合使用,props传参具有的 props篡改、无法兄弟传参的缺点他也有。
4. 应用场景
解决使用在props的应用场景下的跨层跨层通信场景。