Vue中实现组件之间的通讯方式有不少种,eventBus,props,vuex,v-on,ref...等等。本身在不少时候用的没有章法,简单的想着实现功能就能够了,以致于逻辑混论,后期很难维护。因此给本身作个总结,方法并非惟一,只是我如今认为的解决方案。html
组件通讯包括:子组件与父组件之间,兄弟组件之间,模块之间vue
props是响应式的,能够作数据绑定
index.vue父组件相关的代码
若是传给子组件的是一个变量或者数字,则须要前面加上:(v-bind的缩写)绑定。
这边的imgHeight是一个变量,closeFuction是一个方法vuex
<template> <div> <child :img-width="344" :img-height="imgHeight" title="静态文字" :before-close="closeFuction"></child> </div> </template>
child.vue子组件中的,使用的话直接能够用 this.imgWidth形式获取到。npm
props:{ imgWidth: { type: Number, default: 300 }, imgHeight: { type: Number }, title: { type: String, default: '' }, beforeClose: { type: Function, default: function() { console.warn('need a init submitHandle Prop!'); } } }
由于html对于大小写并不敏感,因此子组件中驼峰命名在父组件中建议采用下划线。传递给子组件的方法相似于一个回调函数。调用以后能够更改父组件中的内容,也算是子组件向父组件通讯了。另外值得注意的是,根据Vue风格指南中建议的,prop 的定义应该尽可能详细,至少须要指定其类型。api
在使用组件时,常常会用到v-model。官方解释是 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据。app
对于v-on 官网解释是用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也能够监听子组件触发的自定义事件。
因此前面的例子咱们改一改dom
index.vue父组件相关的代码异步
<template> <div> <child :img-width="344" :img-height="imgHeight" title="静态文字" @before-close="closeFuction"></child> </div> </template>
closeFuction是一个方法,带参数ide
closeFuction(val) { console.log(val); // todo }
child.vue子组件函数
methods: { doSomething() { // todo const val = '须要传给父组件的参数'; this.$emit('before-close', val); console.log('在须要调用的地方 用this.$emit触发'); } }
理解v-on在组件上的机制,就能够更好的认识到,为何v-model仅仅是个语法糖。
ref 被用来给元素或子组件注册引用信息。就是绑定在普通元素上,则获取到这个DOM元素,如果绑定在组件上,则获取到的是这个组件的Vue实例vm。
同一个vue中:
<div ref="child"></div>
mounted() { let domA = document.querySelector('[ref=child]'); let domB = this.$ref.child; console.log('domA与domB是等价的,可是用$ref能够减小获取dom节点的消耗') }
index.vue
<template> <div> <child ref="childName"></child> </div> </template> ---------- mounted(){ let childData = this.$ref.childName.$data.childData; let childMethod = this.$ref.childName.doSomething; console.log('均可以访问到'); }
child.vue子组件
data(){ return { childData: 'xxxx' } }, methods: { doSomething() { // todo } }
注意:
vue更新数据是异步的,咱们须要等到DOM更新完成,因此使用$ref进行DOM操做的时候,须要放在created的$nextTick(() => {}),或者直接放在mounted。
由于绑定组件的话返回的是vm实例,因此参考实例属性获取想要的数据等。
$refs 也不是响应式的,所以不该该试图用它在模板中作数据绑定。
兄弟组件通讯有两种方法,eventBus,vuex。可是我跟愿意将eventBus放在模块之间的通讯来说。
当非父子组件之间通讯较多时,用eventBus很容易逻辑混乱,较难维护。vuex将状态管理单独拎出来,应用统一的方式进行处理,能够理解为组件间公用的一个全局对象。
使用Vuex
安装
npm install --save vuex
其实通常来讲,用到vuex的时候,业务逻辑都已经比较复杂,因此我就讲我本身在用的时候,项目文件的处理。
store/index.js
import Vuex from 'vuex'; import Vue from 'vue'; Vue.use(Vuex); console.log("必须引入vuex"); const store = new Vuex.Store({ state: { stateName: 'xxxx' }, mutations: { mutationsName(state, {params}) { state.stateName = params; console.log("只有在mutations中才能直接改变state中的值") } }, actions: { actionName({ state,commit}, {params}) { let actionParam = 'mmm'; commit('mutationsName', actionParam ); console.log(" 触发mutation 方法要用commit分发,以此改变state"); } } }); export default store;
main.js
console.log("store为实例化生成的"); import store from './store/index.js'; new Vue({ el: '#app', store, console.log("将store挂载到vue实例上") render: h => h(App) })
在组件中使用
child.vue js部分
import { mapActions, mapMutations, mapState } from 'vuex'; export default { computed: { ...mapState({ stateName }) }, methods: { ...mapActions(['actionName']), ...mapMutations(['mutationName']) console.log("使用辅助函数mapMutations直接将触发函数映射到methods上") } // 接下来在实例中就能够用this.stateName,this.actionName来调用 }
当兄弟组件不少,涉及到的处理数据庞大的时候,能够用到vuex中的modules,使得结构更加清晰
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
vuex讲细篇幅很长,更多更复杂的内容,参考官方教程
eventBus的原理是引入一个新的vue实例,而后经过分别调用这个实例的事件触发和监听来实现通讯和参数传递。
eventBus.js 通常会直接用公共一个文件来存放vue实例
import Vue from 'vue'; export default new Vue();
咱们在apple.vue中监听一个事件
apple.vue
import eventBus from 'eventBus.js'; // 咱们在create钩子中监听方法 create(){ console.log("this.getTarget是一个带参数的方法,可是这边只要将二者关联起来"); eventBus .$on('getTarget', this.getTarget); }, beforeDestroy() { console.log("组件销毁前须要解绑事件。不然会出现重复触发事件的问题"); bus.$off('getTarget', this.getTarget); }, methods: { getTarget(param) { // todo } }
在orange.vue中触发
import eventBus from 'eventBus.js'; // 必须引入同一个实例 methods: { doSomething() { eventBus.$emit("getTarget", 22); console.log("向getTarget方法传参22"); } }
eventBus其实很是方便,任何的组件通讯都能用它来完成。可是,咱们会根据状况来选择更易维护的方式。由于eventBus比较很差找到对应的监听或者触发事件具体实现的地方,因此通常组件通讯更考虑上面的实现方式。在模块之间通讯利用eventBus,而后在模块内部,利用vuex通讯,维护数据,会在逻辑上比较清晰。