默认情况下,Vue 组件之间的通信是通过使用 props 来实现的。Props 是从父组件传递到子组件的属性。例如,以下是一个组件,其中 title
是一个 prop
<blog-post title="My journey with Vue"></blog-post>
Props 始终是从父组件传递到子组件。随着应用程序复杂度的增加,您会逐渐遇到所谓的 props 钻取 这里有一篇相关的文章 侧重于 React,但也完全适用)。**Props 钻取**指的是将 props 一层层向下传递到子组件的过程——正如您可能想象的那样,这通常是一个繁琐的过程。
因此,繁琐的 props 钻取可能是复杂应用程序中的一个潜在问题。另一个问题与不相关组件之间的通信有关。我们可以通过使用**事件总线**来解决所有这些问题。
什么是事件总线?好吧,从名称本身就可以概括出来。它是一种传输方式,可以让一个组件将 props 从一个组件传递到另一个组件,无论这些组件在树中的什么位置。
实践任务:构建计数器
让我们一起构建一些内容来演示事件总线的概念。一个可以添加或减去提交的值并统计总计的计数器是一个不错的起点。
查看 CodePen 上 Kingsley Silas Chijioke (@kinsomicrote) 编写的 Vuejs 事件总线计数器。
要使用事件总线,我们首先需要像这样初始化它
import Vue from 'vue';
const eventBus = new Vue();
这将 Vue 的一个实例设置为 eventBus
。您可以根据需要为它命名。如果您正在使用单文件组件,那么您应该在单独的文件中包含代码片段,因为无论如何您都需要导出分配给 eventBus
的 Vue 实例。
import Vue from 'vue';
export const eventBus = new Vue();
完成此操作后,我们就可以开始在我们的计数器组件中使用它了。
以下是我们想要做的事情
- 我们想要一个初始值为 0 的计数器。
- 我们想要一个接受数值的输入字段。
- 我们想要两个按钮:一个在单击时将提交的数值添加到计数器,另一个在单击时从计数器中减去提交的数值。
- 我们想要确认计数器发生变化时的结果。
以下是包含所有这些元素的模板
<div id="app">
<h2>Counter</h2>
<h2>{{ count }}</h2>
<input type="number" v-model="entry" />
<div class="div__buttons">
<button class="incrementButton" @click.prevent="handleIncrement">
Increment
</button>
<button class="decrementButton" @click.prevent="handleDecrement">
Decrement
</button>
</div>
<p>{{ text }}</p>
</div>
我们将输入字段绑定到名为 entry
的值,我们将使用它来增加或减少计数,具体取决于用户输入的内容。当单击任一按钮时,我们将触发一个方法,该方法应增加或减少计数的值。最后,包含在 <p>
标签中的 {{ text }}
东西是我们将在其中打印总结计数器变化的消息。
以下是它们在我们脚本中的组合方式
new Vue({
el: '#app',
data() {
return {
count: 0,
text: '',
entry: 0
}
},
created() {
eventBus.$on('count-incremented', () => {
this.text = `Count was increased`
setTimeout(() => {
this.text = '';
}, 3000);
})
eventBus.$on('count-decremented', () => {
this.text = `Count was decreased`
setTimeout(() => {
this.text = '';
}, 3000);
})
},
methods: {
handleIncrement() {
this.count += parseInt(this.entry, 10);
eventBus.$emit('count-incremented')
this.entry = 0;
},
handleDecrement() {
this.count -= parseInt(this.entry, 10);
eventBus.$emit('count-decremented')
this.entry = 0;
}
}
})
您可能已经注意到,我们即将通过查看代码来使用事件总线。
首先,我们必须为从一个组件向另一个组件发送事件建立一条路径。我们可以使用 eventBus.$emit()
(其中 emit 是发送的时髦说法)来铺设这条路径。该发送包含在两个方法 handleIncrement
和 handleDecrement
中,这两个方法正在侦听输入提交。并且,一旦发生,我们的事件总线就会向任何请求数据的组件发送 props。

您可能已经注意到,我们正在使用 eventBus.$on()
在 created()
生命周期钩子中侦听这两个事件。在两个事件中,我们都必须传入与我们发出的事件相对应的字符串。这就像特定事件的标识符以及为组件接收数据建立方式的标识符。当 eventBus
识别到已宣布的特定事件时,将调用后面的函数——我们将设置一个文本以显示发生了什么,并在三秒后使其消失。
实践任务:处理多个组件
假设我们正在开发一个个人资料页面,用户可以在其中更新其应用程序的姓名和电子邮件地址,然后在不刷新页面的情况下查看更新。即使我们这次处理两个组件:用户个人资料和提交个人资料更改的表单,也可以使用事件总线流畅地实现此功能。
查看 CodePen 上 Kingsley Silas Chijioke (@kinsomicrote) 编写的 Vuejs 事件总线 2。
这是模板
<div class="container">
<div id="profile">
<h2>Profile</h2>
<div>
<p>Name: {{name}}</p>
<p>Email: {{ email }}</p>
</div>
</div>
<div id="edit__profile">
<h2>Enter your details below:</h2>
<form @submit.prevent="handleSubmit">
<div class="form-field">
<label>Name:</label>
<input type="text" v-model="user.name" />
</div>
<div class="form-field">
<label>Email:</label>
<input type="text" v-model="user.email" />
</div>
<button>Submit</button>
</form>
</div>
</div>
我们将 ids
(user.name
和 user.email
)传递到相应的组件。首先,让我们设置“编辑个人资料”(edit__profile
)组件的模板,该组件包含我们想要传递到接下来将设置的“个人资料”组件的姓名和电子邮件数据。同样,我们在检测到提交事件发生后,建立了一个事件总线来发出该数据。
new Vue({
el: "#edit__profile",
data() {
return {
user: {
name: '',
email: ''
}
}
},
methods: {
handleSubmit() {
eventHub.$emit('form-submitted', this.user)
this.user = {}
}
}
})
此数据将用于在“个人资料”(profile
)组件中以响应方式更新用户个人资料,该组件正在查找当总线到达其集线器时传入的 name
和 email
。
new Vue({
el: '#profile',
data() {
return {
name: '',
email: ''
}
},
created() {
eventHub.$on('form-submitted', ({ name, email}) => {
this.name = name;
this.email = email
})
}
})
他们的行李已经打包。现在他们需要做的就是回家了。
很酷,对吧?即使“编辑个人资料”和“个人资料”组件不相关(或不存在直接的父子关系),它们也可以相互通信,通过相同的事件连接。
一路畅通
我发现事件总线在我想要在应用程序中启用响应性时很有帮助——特别是,在不导致页面刷新的情况下,根据从服务器获得的响应更新组件。发出的事件也可能被多个组件监听。
如果您还有其他使用事件总线的有趣场景,我很乐意在评论中听到它们。🚌
这是一个非常简单的场景,当显示一个新组件并需要显示相同数据时会发生什么?您需要为未来的使用者维护该状态,这就是像 vuex 这样的东西存在的原因。如果您认为需要事件总线,最好从 vuex 开始。
“这是一个非常简单的场景”——我认为事件总线之所以有用就在于此:简单的场景。许多小型窗口小部件和基本交互可以在没有 Vuex 的开销的情况下完成。我见过开发人员过度使用事件总线,从而造成很大的复杂性和混乱……所以关键在于了解哪种工具最适合这项工作。
我同意在大多数情况下开发人员应该依靠 Vuex 来协调复杂的状态。但是,确实有一些合理的情况需要向通用“总线”发出事件。例如,您可能需要创建一个组件库,而无需在其中创建对 Vuex 的硬依赖关系。如果这些事件都向该“总线”发出某种标准化事件,那么它们实际上就为实现者正在使用的任何存储结构创建了一个接口。
不过,我对这里实现的方式有点困惑。为什么要创建整个新的 Vue 实例?为什么不直接向应用程序根发出事件呢?