通信
为什么要通信
由于vue将不同页面分发成不同组件进行开发,所以就不可避免的会遇到组件间的通信问题。
例如,有一个表格组件,和一个弹窗组件。逻辑是点击表格中的一个按钮,跳出弹窗显示该按钮所在行的数据。那如何将表格组件中的行数据,传递给弹窗组件呢?
组件通信,就是指组件与组件之间的数据传递
- 组件的数据是独立分散在不同文件中、不同数据对象中的,无法直接访问其他组件的数据。
- 想使用其他组件的数据,就需要组件通信,让数据像水流一样进行传递
组件间的通信
一个组件,与其他组件的关系有三种情况
- 是其他组件的父组件
- 是其他组件的子组件
- 非父子关系,为兄弟组件
这里着重介绍非父子组件如何使用 “$on” 进行通信
非父子组件之间通信
可以想象,如果两个组件之间没有关系,那就可以手动为他们创造一条 “通道” 进行通信,数据可以在通道中传递。文章最后附上详细代码
创建通道
新建一个js文件,并在其中创建一个vue实例。该实例能被后续的接收方、发送方都能访问。那么该实例就如同一个通道一样,承载着双方的信息实例
// eventBus.js文件
import Vue from 'vue'
const Bus = new Vue()
export default Bus
创建接收方
创建testA.vue作为接收方。关键代码在于在组件创建后(“created”),使用“$on”来监听 Bus中的 “sendMsg” 的事件。 “sendMsg” 就像一位乘客,一但该乘客登上Bus这个通道,作为接收方的testA能立马监听到,并且执行一个对应的回调函数。
//testA.vue的实例内容
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})
}
创建发送方
创建testB.vue作为发送方。关键代码在于组件方法中,使用 “$emit” 方法来触发一个事件。这个过程就好像testB将 “sendMsg” 作为一个乘客送到传输通道Bus中。
//testB.vue的实例内容
methods: {
sendMsgFn() {
Bus.$emit('sendMsg', '测试发送')
},
}
这里的 “$emit” 的第一个参数是事件名称,后续的、额外参数都会传递给事件监听器的回调函数。
interface ComponentPublicInstance {
$emit(event: string, ...args: any[]): void
}
export default {
created() {
this.$emit('foo'); // 仅触发事件
this.$emit('bar', 1, 2, 3); // 带有额外的参数
}
}
效果
实现的效果如图所示,点击B组件的按钮,A组件会收到来自B组件的信息
讨论
发布-订阅
这里的Vue的数据传递的方式,体现了“发布-订阅模型”的思想。A是订阅方,B是发布方。A通过 $on来订阅事件,B通过 $emit来订阅事件。
订阅者、发布者可以不唯一
这里只有A一个订阅者,现实可以多个订阅者,他们都可以监听到sendMsg这个事件,以及传递过来的数据。同理,发布者也可以不唯一,一个接收方可以监听多个发布者(通过写多个 $on语句来完成)。
缺陷
如果每个组件之间都通过一条通道来进行维护,可以想象当设计一个变量需要被多个组件访问、修改时,那么维护之间的通道会变的非常麻烦,不利于数据的共享。因此可以设计一个容器,这个容器里的数据对所有监听它的人开放,对数据修改会在全局起作用,以供对数据进行访问和修改,后续就使用Vuex和Pina来充当该类角色。
附测试代码
App.vue
<template>
<div id="app">
<div>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Hello World!" />
</div>
<div>
<div>
<testA ></testA>
<testC ></testC>
</div>
<testB style="margin: auto;"></testB>
</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import testA from './components/testA.vue'
import testB from './components/testB.vue'
import testC from './components/testC.vue'
export default {
name: 'App',
components: {
HelloWorld,
testA,
testB,
testC,
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
eventBus.js
// eventBus.js文件
import Vue from 'vue'
const Bus = new Vue()
export default Bus
testA.vue
<template>
<div class="base-a">
<p>A组件(接收方)</p>
<p>{{ msg }}</p>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>
<script>
import Bus from '../eventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
msg+="修改"
this.msg = msg
})
},
methods: {
sendMsgFn() {
Bus.$emit('sendAnotherMsg', '测试发送另一条消息');
},
},
}
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
justify-content: center;
}
</style>
testB.vue
<template>
<div class="base-b">
<div>B组件(发送方)</div>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>
<script>
import Bus from '../eventBus'
export default {
methods: {
sendMsgFn() {
Bus.$emit('sendMsg', '测试发送')
},
},
}
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
testC.vue
<template>
<div class="base-a">
<p>C组件(接收方)</p>
<p>{{msg}}</p>
<p>{{AnotherMsg}}</p>
</div>
</template>
<script>
import Bus from '../eventBus'
export default {
data() {
return {
msg: '',
AnotherMsg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
});
Bus.$on('sendAnotherMsg', (AnotherMsg) => {
console.log(AnotherMsg);
this.AnotherMsg = AnotherMsg
});
},
}
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>