Vue2组件通信方式及其应用场景
写在最前:文章转自掘金
一、prop
& this.emit('Method name', value)
1. 优点
父子组件通信方面灵活
2. 缺点
- props 对父组件数据的篡改
- 跨层级通信,兄弟组件通讯困难
3. 应用场景
props
的应用场景很简单,就是正常的父子组件通信
二、this.$xxx
实际操作中会有很大的弊端,而且vue本身也不提倡这种通信方式。而且这种通信方式也有很多风险性。
三、provide inject
1. 基本用法
在父组件上通过provide
将方法,属性,或者是自身实例暴露出去,子孙组件,插槽组件,甚至是子孙组件的插槽组件,通过inject
把父辈provide
引进来。
父组件:
<template>
<div class="father" >
<div>子组件对我说:{{ sonMes }}</div>
<div>孙组件对我说:{{ grandSonMes }}</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>
这里我们通过provide
把本身暴露出去。⚠️⚠️⚠️这里声明的名称要与子组件引进的名称保持一致
子组件
<template>
<div class="son" >
<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>
子组件通过inject
把父组件实例引进来,然后可以直接通过this.father
可以直接获取到父组件,并调用下面的sonSay
方法。
孙组件
<template>
<div class="grandSon" >
<input v-model="mes" /> <button @click="send" >对爷爷组件说</button>
</div>
</template>
<script>
export default {
/* 孙组件 */
name:'grandSon',
/* 引入爷爷组件 */
inject:['father'],
data(){
return {
mes:''
}
},
methods:{
send(){
this.father.grandSonSay( this.mes )
}
}
}
</script>
2. 插槽方式
provide
, inject
同样可以应用在插槽上,我们给父子组件稍微变动一下。
父组件
<template>
<div class="father" >
<div>子组件对我说:{{ sonMes }}</div>
<div>孙组件对我说:{{ grandSonMes }}</div>
<son >
<grandSon/>
</son>
</div>
</template>
<script>
import son from './slotSon'
import grandSon from './grandSon'
export default {
name:'father',
components:{
son, /* 子组件 */
grandSon /* 孙组件 */
},
provide(){
return {
/* 将自己暴露给子孙组件 */
father:this
}
},
data(){
return {
grandSonMes:'', /* 来自子组件的信息 */
sonMes:'' /* 发送给子组件的信息 */
}
},
methods:{
/* 接受孙组件信息 */
grandSonSay(value){
this.grandSonMes = value
},
/* 接受子组件信息 */
sonSay(value){
this.sonMes = value
},
},
}
</script>
子组件
<template>
<div class="son" >
<input v-model="mes" /> <button @click="send" >对父组件说</button>
<slot />
</div>
</template>
达到了同样的通信效果。实际这种插槽模式,所在都在父组件注册的组件,最后孙组件也会绑定到子组件的children下面。和上述的情况差不多。
3. provied
其他用法
provide
不仅能把整个父组件全部暴露出去,也能根据需要只暴露一部分(一些父组件的属性或者是父组件的方法),上述的例子中,在子孙组件中,只用到了父组件的方法,所以我们可以只提供两个通信方法。但是这里注意的是,如果我们向外提供了方法,如果方法里面有操作this
行为,需要绑定this
父组件
provide(){
return {
/* 将通信方法暴露给子孙组件(注意绑定this) */
grandSonSay:this.grandSonSay.bind(this),
sonSay:this.sonSay.bind(this)
}
},
methods:{
/* 接受孙组件信息 */
grandSonSay(value){
this.grandSonMes = value
},
/* 接受子组件信息 */
sonSay(value){
this.sonMes = value
},
},
子组件
/* 引入父组件方法 */
inject:['sonSay'],
methods:{
send(){
this.sonSay(this.mes)
}
},
4. 优缺点
- 组件通信不受到子组件层级的影响
- 适用于插槽,嵌套插槽
- 不适合兄弟通讯
- 父级组件无法主动通信
5. 应用场景
provide-inject
这种通信方式,更适合深层次的复杂的父子代通信,子孙组件可以共享父组件的状态,还有一点就是适合el-form
el-form-item
这种插槽类型的情景。
四、vuex
五、事件总线一 EventBus
EventBus
事件总线, EventBus
所有事件统一调度,有一个统一管理事件中心,一个组件绑定事件,另一个组件触发事件,所有的组件通信不再收到父子组件的限制,那个页面需要数据,就绑定事件,然后由数据提供者触发对应的事件来提供数据。
EventBus
核心思想是事件的绑定和触发,这一点和vue
中 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" >
<input v-model="mes" /> <button @click="send" >对子组件说</button>
<div>子组件对我说:{{ sonMes }}</div>
<son />
<brotherSon />
</div>
</template>
<script>
import son from './son'
import brotherSon from './brother'
import EventBus from './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" >
<div> 父组件对我说:{{ fatherMes }} </div>
<input v-model="mes" /> <button @click="send" >对父组件说</button>
<div>
<input v-model="brotherMes" /> <button @click="sendBrother" >对兄弟组件说</button>
</div>
</div>
</template>
<script>
import EventBus from './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>
和父组件的逻辑差不多,把需要接受数据的方法,通过EventBus
绑定,通过触发eventBus
方法,来向外部传递信息。我们还模拟了兄弟之间通信的场景。我们建立一个兄弟组件。
<template>
<div class="son" > 兄弟组件对我说: {{ brotherMes }} </div>
</template>
<script>
import EventBus from './eventBus'
export default {
/* */
name:'brother',
data(){
return {
brotherMes:''
}
},
mounted(){
/* 绑定事件给兄弟组件 */
EventBus.on('brotherSay',this.brotherSay)
},
methods:{
brotherSay(value){
this.brotherMes = value
}
}
}
</script>
我们可以看到,兄弟组件处理逻辑和父子之间没什么区别。
效果图.gif
2. 优缺点
- 简单灵活,父子兄弟通信不受限制。
- 通信方式不受框架影响
- 维护困难,容易引起连锁问题
- 需要谨小慎微的命令规范
- 不利于组件化开发
3. 应用场景
实现总线这种方式更适合,微信小程序,和基于vue构建的小程序,至于为什么呢,因为我们都知道小程序采用双线程模型(渲染层+逻辑层)(如下图所示),渲染层作用就是小程序wxml
渲染到我们的视线中,而逻辑层就是我们写的代码逻辑,在性能上,我们要知道在渲染层浪费的性能要远大于逻辑层的代码执行性能开销,如果我们在小程序里采用通过props
等传递方式,属性是绑定在小程序标签里面的,所以势必要重新渲染视图层。如果页面结构复杂,可能会造成卡顿等情况,所以我们通过eventBus
可以绕过渲染层,直接有逻辑层讲数据进行推送,节约了性能的开销。