众所周知,Vue主要思想就是组件化开发。因为,在实际的项目开发中,肯定会以组件的开发模式进行。形如页面和页面之间需要通信一样,Vue 组件和组件之间肯定也需要互通有无、共享状态。接下来,我们就悉数给大家展示所有 Vue 组件之间的通信方式。

组件关系

image.png

  • App组件和A组件、A组件和B组件、B组件和C组件形成父子关系
  • B组件和D组件形成兄弟关系
  • App组件和C组件、App和B组件形成了隔代关系(其中的层级可能是多级,既隔多代)

组件通信

这么多的组件关系,那么组件和组件之间又有哪些通信的方式呢?各种方式的区别又是什么?适用场景又是什么呢?

1、props和$emit

这种方式是我们日常开发中应用最多的一种方式。

props以单向数据流的形式可以很好的完成父子组件的通信

所谓单向数据流:就是数据只能通过 props 由父组件流向子组件,而子组件并不能通过修改 props 传过来的数据修改父组件的相应状态。至于为什么这样做,Vue 官网做出了解释:

_所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。_

额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

正因为这个特性,于是就有了对应的 $emit。$emit 用来触发当前实例上的事件。对此,我们可以在父组件自定义一个处理接受变化状态的逻辑,然后在子组件中如若相关的状态改变时,就触发父组件的逻辑处理事件。

  1. let Child = {
  2. template: `<div>
  3. <input type="text" v-model='msg'/>
  4. <button @click='handleClick'>传递</button>
  5. </div>`,
  6. props: ['msg'],
  7. methods: {
  8. handleClick() {
  9. this.$emit('getChildData', '子组件数据')
  10. }
  11. },
  12. }
  13. let Parent = {
  14. data() {
  15. return {
  16. msg: '小马哥',
  17. val:''
  18. }
  19. },
  20. methods: {
  21. getChildData(val) {
  22. this.val = val;
  23. }
  24. },
  25. template: `
  26. <div>
  27. <p>我是一个父组件</p>
  28. <p>我是{{val}}</p>
  29. <Child :msg='msg' @getChildData='getChildData'></Child>
  30. </div>
  31. `,
  32. components: {
  33. Child
  34. }
  35. }
  36. let vm = new Vue({
  37. el: '#app',
  38. template: `
  39. <div>
  40. <Parent></Parent>
  41. </div>
  42. `,
  43. components: {
  44. Parent
  45. }
  46. })
  1. 父传子:父组件传递msg数据给子组件,通过v-bind绑定msg,子组件中直接可以用props接收绑定的数据
  2. 子传父:子组件触发相应的事件,通过$emit触发事件传递数据,父组件中绑定对应的事件,通过$on监听对应的事件 接收子组件传递的数据

2、EventBus - 中央事件总线

如果想实现兄弟组件之间进行通信,在项目规模不大的情况下,完全可以使用中央事件总线EventBus的方式。如果你的项目规模是大中型的,那我们会使用vuex状态管理

EventBus 通过新建一个 Vue 事件 bus 对象,通过 bus.$emit 触发事件 bus.$on 监听触发的事件。

  1. Vue.component('A', {
  2. template: `
  3. <div>
  4. <p>我是A组件</p>
  5. <button @click='handleClick'>A传递到B</button>
  6. </div>
  7. `,
  8. data() {
  9. return {
  10. msg: 'hello 小马哥'
  11. }
  12. },
  13. methods: {
  14. handleClick() {
  15. this.$bus.$emit('globalEvent',this.msg);
  16. }
  17. },
  18. })
  19. Vue.component('B', {
  20. template: `
  21. <div>
  22. <p>我是B组件</p>
  23. <h3>{{aValue}}</h3>
  24. </div>
  25. `,
  26. data() {
  27. return {
  28. aValue: ''
  29. }
  30. },
  31. created () {
  32. this.$bus.$on('globalEvent',(val)=>{
  33. this.aValue = val;
  34. })
  35. },
  36. })
  37. // 定义中央事件总线
  38. let bus = new Vue();
  39. // 将中央事件总线赋值给Vue.prototype中,这样所有组件都能访问到了
  40. Vue.prototype.$bus = bus;
  41. let vm = new Vue({
  42. el: '#app',
  43. template: `
  44. <div>
  45. <A></A>
  46. <B></B>
  47. </div>
  48. `,
  49. })

3、$attrs和$listeners

通过 props 进行组件通信的方式只适合直接的父子组件,如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A直接想传递数据给组件C那就行不通了! 只能是组件A通过 props 将数据传给组件B,然后组件B获取到组件A 传递过来的数据后再通过 props 将数据传给组件C。当然这种方式是非常复杂的,无关组件中的逻辑业务一种增多了,代码维护也没变得困难,再加上如果嵌套的层级越多逻辑也复杂,无关代码越多!

针对这样一个问题,Vue 2.4 提供了 $attrs 和 $listeners 来实现能够直接让组件 A 传递消息给组件 C

  1. Vue.component('A', {
  2. template: `
  3. <div>
  4. <p>我是A组件</p>
  5. <B :msg='msg' @getCData='getCData'></B>
  6. </div>
  7. `,
  8. methods: {
  9. getCData(val) {
  10. alert(val)
  11. }
  12. },
  13. data() {
  14. return {
  15. msg: 'hello 小马哥'
  16. }
  17. },
  18. })
  19. Vue.component('B', {
  20. template: `
  21. <div>
  22. <p>我是B组件</p>
  23. <!-- C组件中能直接触发 getCData 的原因在于:B组件调用 C组件时,使用 v-on 绑定了 $listeners 属性 -->
  24. <!-- 通过v-bind 绑定 $attrs 属性,C组件可以直接获取到 A组件中传递下来的 props(除了 B组件中 props声明的) -->
  25. <C v-bind='$attrs' v-on='$listeners'></C>
  26. </div>
  27. `,
  28. // props: ['msg'],
  29. data() {
  30. return {}
  31. }
  32. })
  33. Vue.component('C', {
  34. template: `
  35. <div>
  36. <p>我是C组件</p>
  37. <p>{{$attrs.msg}}</p>
  38. <button @click='handleClick'>传递数据</button>
  39. </div>
  40. `,
  41. methods: {
  42. handleClick() {
  43. this.$emit('getCData', 'C组件的数据')
  44. }
  45. },
  46. data() {
  47. return {}
  48. }
  49. })
  50. let vm = new Vue({
  51. el: '#app',
  52. template: `
  53. <div>
  54. <A></A>
  55. </div>
  56. `,
  57. })
  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定属性 (class和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。

    4、provid 和 inject

    在父组件中通过 provider 来提供属性,然后在子组件中通过 inject 来注入变量。不论子组件有多深,只要调用了 inject 那么就可以注入在 provider 中提供的数据,而不是局限于只能从当前父组件的 prop 属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。这和 React 中的 Context API 有没有很相似!

    1. Vue.component('A', {
    2. template: `
    3. <div>
    4. <p>我是A组件</p>
    5. <B></B>
    6. </div>
    7. `,
    8. provide:{
    9. a:"祖先A的数据"
    10. },
    11. data() {
    12. return {
    13. msg: 'hello 小马哥'
    14. }
    15. }
    16. })
    17. Vue.component('B', {
    18. template: `
    19. <div>
    20. <p>我是B组件</p>
    21. <C></C>
    22. </div>
    23. `,
    24. data() {
    25. return {}
    26. }
    27. })
    28. Vue.component('C', {
    29. template: `
    30. <div>
    31. <p>我是C组件</p>
    32. <h3>{{a}}</h3>
    33. </div>
    34. `,
    35. inject:['a'],
    36. data() {
    37. return {}
    38. }
    39. })
    40. let vm = new Vue({
    41. el: '#app',
    42. template: `
    43. <div>
    44. <A></A>
    45. </div>
    46. `,
    47. })
  • 在 parent 组件中,通过 provide 属性,以对象的形式向子孙组件暴露了一些属性

  • 在 child 组件中,通过 inject 属性注入了 parent 组件提供的数据,实际这些通过 inject 注入的属性是挂载到 Vue 实例上的,所以在组件内部可以通过 this 来访问。

    ⚠️ 注意:官网文档提及 provide 和 inject 主要为高阶插件/组件库提供用例,并不推荐直接用于应用程序代码中。

5、$parnt 和 $children

这里要说的这种方式就比较直观了,直接操作父子组件的实例。$parent 就是父组件的实例对象,而 $children 就是当前实例的直接子组件实例了,不过这个属性值是数组类型的,且并不保证顺序,也不是响应式的。

  1. Vue.component('Parent', {
  2. template: `
  3. <div>
  4. <p>我是父组件</p>
  5. {{msg}}
  6. <hr/>
  7. <Child></Child>
  8. </div>
  9. `,
  10. mounted () {
  11. //读取子组件数据,注意$children并不保证顺序,也不是响应式的
  12. console.log(this.$children[0].a)
  13. },
  14. data() {
  15. return {
  16. msg: 'hello 小马哥'
  17. }
  18. },
  19. })
  20. Vue.component('Child', {
  21. template: `
  22. <div>
  23. <p>我是孩子组件</p>
  24. <input type="text" @input='changeValue'/>
  25. <h2>{{myMsg}}</h2>
  26. </div>
  27. `,
  28. methods: {
  29. changeValue() {
  30. this.$parent.msg = '子组件中的数据'
  31. }
  32. },
  33. data() {
  34. return {
  35. myMsg:this.$parent.msg,
  36. a:"小马哥"
  37. }
  38. }
  39. })
  40. let vm = new Vue({
  41. el: '#app',
  42. template: `
  43. <div>
  44. <Parent></Parent>
  45. </div>
  46. `,
  47. })

6、Vuex 状态管理

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