@浅谈Vue组件通讯javascript
create by db on 2019-8-15 19:41:55
Recently revised in 2019-9-25 17:11:00php
Hello 小伙伴们,若是以为本文还不错,麻烦点个赞或者给个 star,大家的赞和 star 是我前进的动力!GitHub 地址css
这是一篇欠下好久的文章了。对于以Vue为工做栈的我来讲,Vue组件之间的通讯问题是接触最多的问题之一。所以,参考一些文章,并结合本身的工做及学习经验,写下这篇博客——温故而知新。 做为一只前端菜鸟,本篇文章旨在记录本身的学习心得,若有不足,还请多多指教,谢谢你们。html
I hear and I fogorget.前端
I see and I remember.vue
I do and I understand.java
组件间的通讯是是实际开发中很是经常使用的一环,Vue中实现组件之间的通讯方式有不少种, props,eventBus, Vuex, v-on, ref...等等。如何使用组件通讯,对项目总体设计、开发、规范都有很实际的的做用。Vue文档上以及各种大佬博客中对总结vue组件间通讯都写的很详细了,我也抛砖引玉,浅谈一下vue组件间通讯的几种方式以及各自的使用场景。git
参考文献:github
Vue组件通讯包括:子组件与父组件之间,兄弟组件之间,模块之间vuex
一言不合上代码:
index.vue
父组件
<template>
<div>
<h1>我是父元素</h1>
<child title="静态文字" :img-width="344" :img-height="imgHeight" :before-close="closeFuction" @dadEmit='sonEmit'></child>
</div>
</template>
<script> import child from './child.vue' // 引入子组件 export default { data () { return { imgHeight: 300, } }, components: { child // 声明子组件 }, methods: { closeFuction () { console.log('叫爸爸!') }, sonEmit (msg) { console.log('儿子你说啥?') console.log(msg) } } } </script>
复制代码
child.vue
子组件
<template>
<div class="hello">
<h1>我是子元素</h1>
<h1>{{ title }}</h1>
<button @click="clickEmit">点我给父元素传值</button>
</div>
</template>
<script> export default { name: 'child', // 接收父子组件参数 props: { imgWidth: { type: Number, // 数据类型 default: 300 // 默认值 }, imgHeight: { type: Number }, title: { type: String, default: '' }, beforeClose: { type: Function, default: function () { console.log('你闭嘴'); } } }, created () { console.log(this.imgWidth) console.log(this.imgHeight) this.beforeClose() }, methods: { // $emit给父组件传值 clickEmit () { this.$emit('dadEmit', "你这个糟老头太坏了!") } } } </script>
<style scoped> .hello { background: yellow; } </style>
复制代码
父组件传递数据时相似在标签中写了一个属性,若是是传递的数据是data
中的天然是要在传递属性前加:
(v-bind
的缩写),若是传递的是一个已知的固定值呢
字符串
是静态的可直接传入无需在属性前加:
数字
,布尔
,对象
,数组
,由于这些是js表达式而不是字符串,因此即便这些传递的是静态的,也须要前面加上:
绑定,把数据放到data
中引用,若是prop
传到子组件中的数据是一个对象
的话,要注意传递的是一个对象引用
,虽然父子组件看似是分离的但最后都是在同一对象下。
prop
传到子组件的值只是做为初始值使用,且在父组件中不会变化赋值到data
中使用prop
的数据在父组件会被改变的,放到计算属性中监听变化使用。由于若是传递的是个对象
的话,只改变下面的某个属性子组件中是不会响应式更新的,若是子组件须要在数据变化时响应式更新那只能放到computed
中或者用watch
深拷贝deep:true
才能监听到变化prop
传递数据的变化作些操做,那么写在computed
中会报警告,由于计算属性中不推荐有任何数据的改变,最好只进行计算。若是你非要进行数据的操做那么能够把监听写在watch
(注意deep深拷贝)或者使用computed
的get
和set
。 但问题又来了,若是你传进来的是个对象
,同时你又须要在子组件中操做传进来的这个数据,那么在父组件中的这个数据也会改变,由于你传递的只是个引用, 即便你把prop
的数据复制到data
中也是同样的,不管如何赋值都是引用的赋值,你只能对对象作深拷贝建立一个副本才能继续操做,你能够用JSON的方法先转化字符串在转成对象更方便一点
JSON.stringify(obj)
将JSON对象转为字符串。JSON.parse(string)
将字符串转为JSON对象格式。因此在父子传递数据时要先考虑好数据要如何使用,不然你会遇到不少问题或子组件中修改了父组件中的数据,这是很隐蔽而且很危险的。
m.$emit( eventName, […args] )
参数:
{string} eventName // 父元素中定义的事件名称
[...args] // 参数
// 触发当前实例上的事件。附加参数都会传给监听器回调。
复制代码
以上是Vue官网给$emit
的定义。
简而言之,能够经过监听当前实例上的自定义事件,经过vm.$emit
触发父元素上定义的事件,将[...args]中的参数传给父元素
兄弟组件通讯有两种方法,eventBus
,Vuex
。
eventBus
的原理是引入一个新的vue
实例,而后经过分别调用这个实例的事件触发和监听来实现通讯和参数传递。
eventBus.js
文件 —— 咱们通常会直接用一个公共文件来存放vue实例
import Vue from 'vue';
export default new Vue();
复制代码
咱们在apple.vue
中监听一个事件
apple.vue
文件
<template>
<div class="hello">
<h1>我是苹果</h1>
<h1>{{ title }}</h1>
</div>
</template>
<script> import eventBus from './eventBus.js' export default { name: 'orange', data () { return { title: 300 } }, // 咱们在created钩子中监听方法 created () { // 在created()钩子中调用eventBus监听getTarget事件,并接受参数,绑定方法 eventBus.$on('getTarget', this.getTarget) // eventBus.$on('getTarget', target => { // 也能够在后面直接写方法 // this.title = target // }) }, beforeDestroy () { // 组件销毁前须要解绑事件。不然会出现重复触发事件的问题 eventBus.$off('getTarget', this.getTarget) }, methods: { getTarget (param) { this.title = param } } } </script>
<style scoped> .hello { background: red; } </style>
复制代码
咱们在orange.vue
中触发eventBus
orange.vue
文件
<template>
<div class="hello">
<h1>我是橙子</h1>
<h1>{{ title }}</h1>
<button @click="doSomething">点击触发eventBus</button>
</div>
</template>
<script> import eventBus from './eventBus.js' export default { name: 'orange', data () { return { title: 300 } }, methods: { // $emit触发事件getTarget事件 doSomething () { // 向getTarget方法传参22 eventBus.$emit('getTarget', 22) } } } </script>
<style scoped> .hello { background: orange; } </style>
复制代码
eventBus
其实很是方便,任何的组件通讯都能用它来完成。可是,咱们会根据状况来选择更易维护的方式。由于eventBus
比较很差找到对应的监听或者触发事件具体实现的地方,因此通常组件通讯更考虑Vuex
的实现方式。
在模块之间通讯利用eventBus
,而后在模块内部,利用Vuex
通讯,维护数据,会在逻辑上比较清晰。
当非父子组件之间通讯较多时,用eventBus
很容易逻辑混乱,较难维护。Vuex
将状态管理单独拎出来,应用统一的方式进行处理,能够理解为组件间公用的一个全局对象。
安装vuex,使用命令:
npm install vuex --save
store/index.js
import Vuex from 'vuex' // 引入Vuex
import Vue from 'vue' // 引入Vue
// 使用Vuex
Vue.use(Vuex)
// 建立Vuex实例
const store = new Vuex.Store({
// state:vuex中的数据源,咱们须要保存的数据就保存在这里,能够在页面经过 this.$store.state.stateName来获取咱们定义的数据;
state: {
stateName: '哈哈哈'
},
// mutations:修改store中的值惟一的方法就是提交mutation,能够在组件中使用 this.$store.commit('xxx') 提交 mutation
mutations: {
mutationsName (state, params) { // 定义更改state的方法,能够传参,必须是同步函数
state.stateName = params
}
},
// Action 提交的是 mutation,而不是直接变动状态。Action 能够包含任意异步操做。相似于vue的methods。能够在组件中使用this.$store.dispatch('actionName', 'xxx')分发
actions: {
actionName (contest, params) { // 触发mutation 方法要用commit分发,以此改变state
contest.commit('mutationsName', params)
}
},
// getters:至关于Vue中的computed,能够用于监听、state中的值的变化,返回计算后的结果。能够在组件中使用this.$store.getters.getStateName获取其中的值
getters: {
getStateName: state => {
return state.stateName
}
}
})
export default store // 导出store
复制代码
main.js
文件
// store为实例化生成的
import store from './store/index.js';
new Vue({
el: '#app',
store, // 将store挂载到vue实例上
render: h => h(App)
})
复制代码
若是咱们不喜欢这种在页面上使用
this.$store.state.stateName
this.$store.getters.getStateName
this.$store.dispatch('actionName', 'xxx')
这种很长的写法,那么咱们可使用mapState
、mapGetters
、mapActions
就不会这么麻烦了;
child.vue
文件
<template>
<div class="hello">
<h1>我是香蕉</h1>
mapState取值:<h2>{{ stateName }}</h2>
mapGetters取值:<h2>{{ getStateName }}</h2>
<button @click="clickAction">使用action改值</button>
<button @click="clickMutation">使用mutation改值</button>
<button @click="clickCommit">使用commit改值</button>
<button @click="clickDispatch">使用dispatch改值</button>
</div>
</template>
<script> import { mapActions, mapMutations, mapState, mapGetters } from 'vuex' export default { name: 'banana', computed: { ...mapGetters(['getStateName']), ...mapState(['stateName']) }, methods: { // 使用辅助函数直接将触发函数映射到methods上 ...mapActions(['actionName']), ...mapMutations(['mutationsName']), // 点击触发 clickAction () { this.actionName('使用action改值') }, clickMutation () { this.mutationsName('使用mutation改值') }, clickCommit () { this.$store.commit('mutationsName', '使用commit改值') }, clickDispatch () { this.$store.dispatch('actionName', '使用dispatch改值') } } } </script>
<style scoped> .hello { background: yellow; } </style>
复制代码
当兄弟组件不少,涉及到的处理数据庞大的时候,能够用到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讲细篇幅很长,更多更复杂的内容,参考官方教程
路漫漫其修远兮,与诸君共勉。
后记:Hello 小伙伴们,若是以为本文还不错,记得点个赞或者给个 star,大家的赞和 star 是我编写更多更丰富文章的动力!GitHub 地址
db 的文档库 由 http://www.javashuo.com/tag/db 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/danygitgit上的做品创做。
本许可协议受权以外的使用权限能够从 creativecommons.org/licenses/by… 处得到。