组件之间的常用通信方式一般有六种(props、自定义事件、全局事件总线$bus、pubsub、vuex、插槽)。其中不乏有父传子的、子传父的,还有全局都能够使用的。着重理解props、自定义事件与全局总线$bus。

props 和 $emit

用过 Vue 技术栈开发项目过的开发者对这样一个组合肯定不会陌生,这种组件通信的方式是我们运用的非常多的一种。props 以单向数据流的形式可以很好的完成父子组件的通信。
所谓单向数据流:就是数据只能通过 props 由父组件流向子组件,而子组件并不能通过修改 props 传过来的数据修改父组件的相应状态。至于为什么这样做,Vue 官网做出了解释:
所有的 prop 都使得其父子 prop 之间形成了一个**单向下行绑定**:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
_额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
正因为这个特性,于是就有了对应的 $emit。$emit 用来触发当前实例上的事件。对此,我们可以在父组件自定义一个处理接受变化状态的逻辑,然后在子组件中如若相关的状态改变时,就触发父组件的逻辑处理事件。

中央事件总线 EventBus

对于父子组件之间的通信,上面的两种方式是完全可以实现的,但是对于两个组件不是父子关系,那么又该如何实现通信呢?在项目规模不大的情况下,完全可以使用中央事件总线 EventBus 的方式。如果你的项目规模是大中型的,那你可以使用我们后面即将介绍的 Vuex 状态管理
EventBus 通过新建一个 Vue 事件 bus 对象,然后通过 bus.$emit 触发事件,bus.$on 监听触发的事件。

  1. // 组件 A
  2. Vue.component('A', {
  3. template: `
  4. <div>
  5. <p>this is A component!</p>
  6. <input type="text" v-model="mymessage" @input="passData(mymessage)">
  7. </div>
  8. `,
  9. data() {
  10. return {
  11. mymessage: 'hello brother1'
  12. }
  13. },
  14. methods: {
  15. passData(val) {
  16. //触发全局事件globalEvent
  17. this.$EventBus.$emit('globalEvent', val)
  18. }
  19. }
  20. });
  21. // 组件 B
  22. Vue.component('B', {
  23. template:`
  24. <div>
  25. <p>this is B component!</p>
  26. <p>组件A 传递过来的数据:{{brothermessage}}</p>
  27. </div>
  28. `,
  29. data() {
  30. return {
  31. mymessage: 'hello brother2',
  32. brothermessage: ''
  33. }
  34. },
  35. mounted() {
  36. //绑定全局事件globalEvent
  37. this.$EventBus.$on('globalEvent', (val) => {
  38. this.brothermessage = val;
  39. });
  40. }
  41. });
  42. //定义中央事件总线
  43. const EventBus = new Vue();
  44. // 将中央事件总线赋值到 Vue.prototype 上,这样所有组件都能访问到了
  45. Vue.prototype.$EventBus = EventBus;
  46. const app = new Vue({
  47. el: '#app',
  48. template: `
  49. <div>
  50. <A />
  51. <B />
  52. </div>
  53. `
  54. });

在上面的实例中,我们定义了组件 A 和组件 B,但是组件 A 和组件 B 之间没有任何的关系。

  • 1)、 首先我们通过 new Vue() 实例化了一个 Vue 的实例,也就是我们这里称呼的中央事件总线 EventBus ,然后将其赋值给了 Vue.prototype.$EventBus,使得所有的业务逻辑组件都能够访问到;
  • 2)、 然后定义了组件 A,在组件 A 里面定义了一个处理的方法 passData,主要定义触发一个全局的 globalEvent 事件,并传递一个参数;
  • 3). 最后定义了组件 B,在组件 B 里面的 mounted 生命周期监听了组件 A 里面定义的全局 globalEvent 事件,并在回调函数里面执行了一些逻辑处理。

中央事件总线 EventBus 非常简单,就是任意组件和组件之间打交道,没有多余的业务逻辑,只需要在状态变化组件触发一个事件,然后在处理逻辑组件监听该事件就可以。该方法非常适合小型的项目!

provide 和 inject

熟悉 React 开发的同学对 Context API 肯定不会陌生吧!在 Vue 中也提供了类似的 API 用于组件之间的通信。
在父组件中通过 provider 来提供属性,然后在子组件中通过 inject 来注入变量。不论子组件有多深,只要调用了 inject 那么就可以注入在 provider 中提供的数据,而不是局限于只能从当前父组件的 prop 属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。这和 React 中的 Context API 有没有很相似!

  1. // 定义 parent 组件
  2. Vue.component('parent', {
  3. template: `
  4. <div>
  5. <p>this is parent component!</p>
  6. <child></child>
  7. </div>
  8. `,
  9. provide: {
  10. for:'test'
  11. },
  12. data() {
  13. return {
  14. message: 'hello'
  15. }
  16. }
  17. });
  18. // 定义 child 组件
  19. Vue.component('child', {
  20. template: `
  21. <div>
  22. <input type="tet" v-model="mymessage">
  23. </div>
  24. `,
  25. inject: ['for'], // 得到父组件传递过来的数据
  26. data(){
  27. return {
  28. mymessage: this.for
  29. }
  30. },
  31. });
  32. const app = new Vue({
  33. el: '#app',
  34. template: `
  35. <div>
  36. <parent />
  37. </div>
  38. `
  39. });

在上面的实例中,我们定义了组件 parent 和组件 child,组件 parent 和组件 child 是父子关系。

  • 1)、 在 parent 组件中,通过 provide 属性,以对象的形式向子孙组件暴露了一些属性
  • 2)、 在 child 组件中,通过 inject 属性注入了 parent 组件提供的数据,实际这些通过 inject 注入的属性是挂载到 Vue 实例上的,所以在组件内部可以通过 this 来访问。

⚠️ 注意:官网文档提及 provide 和 inject 主要为高阶插件/组件库提供用例,并不推荐直接用于应用程序代码中。
关于 provide 和 inject 这对属性的更多具体用法可以参照官网的文档

Vuex 状态管理

Vuex 是状态管理工具,实现了项目状态的集中式管理。工具的实现借鉴了 FluxRedux、和 The Elm Architecture 的模式和概念。当然与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。详细的关于 Vuex 的介绍,你既可以去查看官网文档,也可以查看本专栏关于 Vuex 一系列的介绍。