Vue组件之间的常用通信方式

217 阅读3分钟

在日常的使用Vue开发工作中,经常遇到组件之间的数据通信,以下是个人总结的组件之间的通信方式:

一、props / $emit

1、 父组件通过props的方式向子组件传递数据

这里有父组件A,子组件B,父组件A向子组件B传递数据如下:

<!-- A.vue -->
<template>
  <div class="a-wrap">
    <h2>我是A组件</h2>
    <B :msg = 'msgA'/>
  </div>
</template>

<script>
import B from "./B";
export default {
  name: "A",
  data() {
    return {
      msgA: "我是来自父组件A的数据 msg"
    };
  },
  components: {
    B
  }
};
</script>

<!-- B.vue -->
<template>
  <div class="b-wrap">
    <h4>
      我子组件 B:<span class="color-red">{{msg}}</span>  
    </h4>
  </div>
</template>
<script>
export default {
  name: 'B',
  props: {
    msg: ''
  },
}
</script>
<style lang="stylus" scoped>
.color-red
  color red
</style>

注: props 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 props 只读,不可被修改。props传递数据类型以及类型校验等,详见官网。这里只是简单的父子组件嵌套,多层嵌套这样传值不宜。 多层嵌套,详见如下方法。

2、 子组件通过$emit 可以向父组件通信

子组件通过emit向父组件派发一个事件,父组件通过监听,接收到数据;

<!-- A.vue -->
<template>
  <div class="a-wrap">
    <h2>我是A组件</h2>
    <h3 v-if="getBdata">接收到了B的数据--------》{{getBdata}}</h3>
    <B :msg = 'msgA' @getBcomponetHand = 'getBdataHand'/>
  </div>
</template>

<script>
import B from "./B";
export default {
  name: "A",
  data() {
    return {
      msgA: "我是来自父组件A的数据 msg",
      getBdata: ''
    };
  },
  methods: {
    getBdataHand(data) {
       this.getBdata = data
    }
  },
  components: {
    B
  }
};
</script>

<!-- B.vue -->
<template>
  <div class="b-wrap">
    <h4>
      我子组件 B:<span class="color-red">{{msg}}</span>  
    </h4>
    <div>
        <button @click="emitHand">B子组件 向 父组件A 传值</button>
    </div>
  </div>
</template>
<script>
export default {
  name: 'B',
  props: {
    msg: ''
  },
  methods: {
    emitHand() {
      this.$emit('getBcomponetHand','我是来自子组件B的数据')  
    }
  }
}
</script>
<style lang="stylus" scoped>
.color-red
  color red
</style>

二、provide/ reject

provide/ reject是vue的2.2.0新增,在祖先组件中通过provide,向其所有子孙后代注入一个依赖,不论组件层次有多深,然后子组件可以通过reject来注入。详见官网

例如:有简单的A,B,C三个组件,嵌套关系如下A -> B -> C(实际开发中有可能嵌套多层,不过在开发中不建议多层组件嵌套)。

<-- A.vue -->
<template>
  <div class="a-wrap">
    <h2>我是A组件</h2>
    <div>
        我是B组件
        <B />
    </div>
  </div>
</template>
<script>
import B from "./B";
export default {
  name: "A",
  provide: {
    msgACom:'我是来自组件A的数据 magA AAAA'
  },
  components: {
    B
  }
};
</script>
<-- B.vue -->
<template>
  <div class="b-wrap">
    <h4>
      组件B:<span class="color-red">{{msgACom}}</span>  
    </h4>
    <C />
  </div>
</template>
<script>
import C from "./C";
export default {
  name: 'B',
  inject: ['msgACom'],
  provide() {
    return {
      msgBCom: '我是组件B 通过provide 传递过来的',
      msgBList: ['组件B传递过来的B1','组件B传递过来的B2','组件B传递过来的B3']
    }
  },
  components: {
    C 
  }
}
</script>
<style lang="stylus" scoped>
.color-red
  color red
</style>
<-- C -->
<template>
  <div class="c-wrap">
    我是组件C
    <h4>
       <div>组件 C:<span class="color-red">{{msgACom}}</span>  </div>
       <div>组件C:<span class="color-red">{{msgBCom}}</span></div>
       <ul>
         <li v-for="(item, index) in msgBList" :key="index">组件C:{{item}}</li>
       </ul>
    </h4>
  </div>
</template>
<script>
export default {
  name: 'C',
  inject: ['msgACom','msgBCom','msgBList'],
}
</script>
<style lang="stylus" scoped>
.color-red
  color red
</style>

三、eventBus

event-bus 又称为z中央事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。
例子如下:在这里父组件A包含B,C兄弟组件,在组件B中通过点击按钮,让C组件的数字每次加一,代码结构如下。

<template>
  <div class="a-wrap">
    <h2>我是A组件</h2>
    <div>
        我是B组件
        <B />
    </div>
    <div>
        我是C组件
        <C />
    </div>
  </div>
</template>

<script>
import B from "./B";
import C from "./C";
export default {
  name: "A",
  components: {
    B,
    C
  }
};
</script>

  • 1、首先初始化一个EventBus事件,建立一个EventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
  • 2、在B.vue定义派发的事件 cComAddHand
<template>
  <div class="b-wrap">
    <h4>
      组件B:
      <button class="color-red" @click="addNumHand">点击增加 +1</button>  
    </h4>
  </div>
</template>
<script>
import {EventBus} from './EventBus'
export default {
  name: 'B',
  data() {
      return {
          num: 1
      }
  },
  methods: {
    addNumHand() {
      EventBus.$emit('cComAddHand', {
          number:  ++this.num
      })
    }
  }
}
</script>
<style lang="stylus" scoped>
.color-red
  color red
</style>

  • 3、在C.vue定义接收的事件 cComAddHand
<template>
  <div class="c-wrap">
    <h4>
       <div>组件 C:<span class="color-red">{{count}}</span>  </div>
    </h4>
  </div>
</template>
<script>
import {EventBus} from './EventBus'
export default {
  name: 'C',
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    EventBus.$on('cComAddHand', res => {
      this.count = res.number
      console.log(res,'CCC')
    })
  },
  beforeDestroy () {
    // 销毁监听的事件
     EventBus.$off('cComAddHand')
  }
}
</script>
<style lang="stylus" scoped>
.color-red
  color red
</style>


四、Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上。这对于Vue开发者,必备的技能,这里不再叙述。详见

五、dispatch/boardcast

dispatch 和broadcast属性,在 Vue 1.0 中主要用来实现基于组件树结构的事件流通信 —— 通过向上或向下以冒泡的形式传递事件流,以实现嵌套父子组件的通信。但是由于其显功能缺陷,在 Vue 2.0 中就被移除了。虽然 Vue 官网已经不再支持使用 dispatch 和broadcast 进行组件通信,但是在很多基于 Vue 的 UI 框架中都有对其的封装,包括 element-ui、iview 等等。本例子参考了element-ui 源码 例如:A.vue -> B.vue -> C.vue 依次嵌套;假设A,B,C....等多个组件嵌套时,用dispatch/boardcast如何通信呢。

  • 1、子组件向父组件传递数据时,通过dispatch方法, 获取父级,在向上传递,一直递归获取parent,向父级dispatch事件,父级通过$on来监听。
  • 2、父组件向值组件传递数据时,通过boardcast一直想自己广播事件,子级组件也是通过$on来监听。

在mixins文件新建一个emitter.js文件,如下

function boardcast(eventName, data) {
  this.$children.map(child => {
    child.$emit(eventName, data);
    if (child.$children.length) {
      boardcast.call(child, eventName, data);
    }
  });
}
export default {
  methods: {
    dispatch(eventName, data) {
      // 向上传递,一直递归获取parent,派发事件
      let parent = this.$parent;
      while (parent) {
        if (parent) {
          parent.$emit(eventName, data);
          parent = parent.$parent;
        }
      }
    },
    boardcast(eventName, data) {
      // 向下传递,一直向每个子组件广播
      boardcast.call(this, eventName, data);
    }
  }
};

A.vue文件中。
父组件向子组件传值时,首先通过mixins中boardcast广播cComHand事件,然后在C.vue通过on来监听cComHand;
 子组件向父组件传值时,通过dispatch向父级派发aComHand事件,然后父组件on来监听aComHand,来获取数据。

注:封装的boardcast/dispatch,在这里是封装在mixins里;也可以把这两个方法挂在Vue.protopyte中,然后其子组件都可以通过this.$boardcast、this.$dispatch来调用。根据项目需要,来使用哪一种方式。

<template>
 <div class="a-wrap">
   <h2>
       我是A组件
       <button @click="boardcastHand">点击我,我会向子组件C传值</button>
       {{content}}
   </h2>
   <div>
       我是B组件
       <B />
   </div>
 </div>
</template>

<script>
import emmitter from '@/mixins/emitter'
import B from "./B";
export default {
 name: "A",
 mixins: [emmitter],
 data() {
   return {
       content: ''
   }
 },
 mounted() {
   // 子传父:监听来自子级组件传过来的事件
   this.$on('aComHand', data => {
     this.content = data.cMsg
   })
 },
 methods: {
   boardcastHand() {
       // 父传子:向子组件广播事件
       this.boardcast('cComHand', {
           msg: '我是A组件传过来的'
       })
   }
 },
 components: {
   B
 }
};
</script>

C.vue文件

<template>
 <div class="c-wrap">
   <h4>
      <div>
        <div>组件 C:<span class="color-red">{{content}}</span>  </div>
        <div><button @click="dispatchHand">点击我我会向父级组件传递数据</button></div>
      </div>
   </h4>
 </div>
</template>
<script>
import emmitter from '@/mixins/emitter'
export default {
 name: 'C',
 mixins: [emmitter],
 data() {
   return {
     content: ''
   }
 },
 mounted() {
   // 父传子:监听来自父级组件传过来的事件
   this.$on('cComHand', data => {
     this.content = data.msg
   })
 },
 methods: {
   dispatchHand() {
     // 子传父:向父级派发事件
     this.dispatch('aComHand', {
       cMsg: '我是来自C组件的数据'
     })
   }
 }
 
}
</script>
<style lang="stylus" scoped>
.color-red
 color red
</style>