Vue 组件通信
组件作为Vue中的核心概念,是值得我们深入研究的课题之一,通过研究它,我们可以理解更高深的思想,可以提升自己的开发技巧。而今天,我要讨论的是Vue的组件通信。
众所周知,组件通信是通过props和emit去完成的,但实际上,这只是众多方式中的一种而已。而且针对不同的情况,会有更合适的方法。下面就听我慢慢道来。
1.props和emit
父组件:
<template>
<div class="parent-box">
<h3>我是父元素,props方式</h3>
<p class="content">
通过$emit获得子元素属性{{children2}}
</p>
<Children2 @changeChild2="changeChild2"></Children2>
</div>
</template>
<script>
data(){
return {
children2:'children2',
}
},
methods:{
changeChild2(val){
this.children2 = val
},
}
</script>
子组件
<template>
<div class="children-box">
<h4>我是子元素</h4>
<button @click="clickEvent">改值</button>
<p>通过props通信 {{value}}</p>
</div>
</template>
<script>
export default {
props:['value'],
methods:{
clickEvent(){
// 核心代码
this.$emit('changeChild2',Math.random())
}
}
}
</script>
大体效果如下:
20190407_211309.gif
可以看到,通过点击按钮,可以改变通过props传入子组件的value属性。因为这种方式是大家最常用的一种方式,这里就不做详细解释了。
2.$parent
和 $children
$parent
属性可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。
$children
可以访问当前实例的直接子组件。
下面来看一个例子,代码如下,注意注释部分。
父组件
<template>
<div class="parent-box">
<h3>我是父元素,$parent,$children方式</h3>
<p class="content">
通过$children获得子元素属性{{children1}}
</p>
<Children1></Children1>
</div>
</template>
<script>
data(){
return {
parent1:'parent1',
children1:'children1',
}
},
mounted() {
// 核心代码,通过$children获取子组件的属性
this.children1 = this.$children[0]._data.value
},
</script>
子组件
<template>
<div class="children-box">
<h4>我是子元素</h4>
<input type="text" v-model="parent">
<p>通过$parent获取父元素的属性 {{parent}}</p>
</div>
</template>
<script>
export default {
data(){
return {
// 核心代码,通过$parent获得父组件的属性
parent:this.$parent._data.parent1
}
},
mounted() {
},
watch:{
parent(val){
// 核心代码,改变父组件中的属性
this.$parent._data.parent1 = val
}
}
}
</script>
大体效果如下:
20190407_212944.gif
可以看到,通过this.$parent._data.parent1 = val,改变子组件中parent的值,然后赋值给父组件的parent1可以直接改变父组件的属性值。
虽然这种方式比较方便快捷,但有很大的副作用,就如官网所说:
在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。
节制地使用
$parent
和$children
- 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信
3.总线方式
有时候,我们的组件并不止父子关系这么简单,可能兄弟组件之间也要进行通信,而EventBus就能解决这个问题,相对于vuex它更轻量,不需要我们引入vuex这个庞然大物,更加适合小型项目。
我们在主实例App之外,单独定义一个空的Bus实例,来进行组件间的通信。
下面来看一个例子,代码如下
bus.js
// 核心代码
import Vue from 'vue'
var Bus = new Vue()
export default Bus
父组件:
<template>
<div class="parent-box">
<h3>我是父元素,总线方式</h3>
<Children31></Children31>
<Children32></Children32>
</div>
</template>
子组件1
<template>
<div class="children-box">
<h4>我是子元素</h4>
<p>通过总线方式通信 {{msg}}</p>
</div>
</template>
<script>
import Bus from '../bus'
export default {
data(){
return {
msg:'hello world'
}
},
mounted() {
},
created(){
// 核心代码,接受事件
Bus.$on('setMsg',val=>{
this.msg = val
})
},
}
</script>
子组件2
<template>
<div class="children-box">
<h4>我是子元素</h4>
<input type="text" v-model="msg">
<p>通过总线方式通信 {{msg}}</p>
</div>
</template>
<script>
import Bus from '../bus'
export default {
data(){
return {
msg:'hello world'
}
},
mounted() {
},
watch:{
msg:function (newVal) {
// 核心代码发出事件
Bus.$emit('setMsg',newVal)
},
}
}
</script>
大体效果如下:
20190407_215821.gif
可以看到,我改变子组件2 input的值会触发emit事件,去改变子组件1中的msg。
4.$attrs
和$listeners
$attrs
和$listeners
是2.4.0才新加入的方法,用来解决组件的跨级传输非常有用。试想有A、B、C三个组件,A包含B,B包含C,如果我想在A上给C传参,并且接收C的事件怎么办呢?
原先,我们只使用pros去传参的话,就只能拿B作为中转组件,B组件定义足够多的pros,不仅仅用于自身,还要用于传输给C,而事件的传递,也只能一层层地往上传,这样就会使代码很繁琐,臃肿,不利于维护。
而$attrs
和$listeners
就是用来处理这种情况的,代码如下
父组件A
<template>
<div class="parent-box">
<h3>我是父元素A,$attrs,$listeners方式</h3>
<Children4 :value1="value1" :value2="value2" @clickEvent1="clickEvent1" @clickEvent2="clickEvent2"></Children4>
</div>
</template>
<script>
data(){
value1:'B',
value2:'C',
},
methods:{
clickEvent1(){
this.value1 = Math.random()
},
clickEvent2(){
this.value2 = Math.random()
}
}
</script>
子组件B
<template>
<div class="children-box">
<h4>我是子元素B</h4>
<p>{{value1}}</p>
<button @click="clickEvent">改变value1的值</button>
<Children42 v-bind="$attrs" v-on="$listeners"></Children42>
</div>
</template>
<script>
import Children42 from './Children4.2'
export default {
name:'Children41',
inheritAttrs:false,
props:['value1'],
components:{
Children42
},
methods:{
clickEvent(){
this.$emit('clickEvent1')
}
},
}
</script>
子组件C
<template>
<div class="children-box">
<h4>我是子元素C</h4>
<p>{{value2}}</p>
<button @click="clickEvent">改变value1的值</button>
</div>
</template>
<script>
export default {
name:'Children42',
inheritAttrs:false,
props:['value2'],
data(){
return {
}
},
mounted() {
},
methods:{
clickEvent(){
this.$emit('clickEvent2')
}
},
}
</script>
大体效果如下:
20190407_221354.gif
可以看到,我们只要在引用C组件的时候,加入v-bind="$attrs
" v-on="$listeners
"两个属性即可,这样,C组件就可以接收到来自A组件的值,A组件也能接收到来自C组件的事件。
如此以来,就不需要在B组件定义中转的属性和方法,如果你的组件结构比较复杂,这种方式可以很大程度减少代码的冗余,更加的轻量化。
5.provide和inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
这相对于attrs和listeners可能更加简介,只需要父组件提供变量,子组件注入就行,不需要在中间组件写什么代码,但并不推荐在业务代码中使用,正如官方所说。
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
因为provide inject 会有一个类似冒泡的特性,数据源有可能在中间被”“打断”,甚至是有可能被组件库中的组件打断,或者打断组件库中的provide,不利于维护
代码如下:
父组件
<template>
<div class="parent-box">
<h3>我是父元素,provide,inject方式</h3>
<Children5></Children5>
</div>
</template>
<script>
data(){
return {
theme:'blue'
},
// 核心代码
provide(){
return {
test:this
}
},
}
</script>
子组件
<template>
<div class="children-box">
<h4 :style="{color:value.theme}">我是子元素</h4>
<div @click="changeValue">改颜色</div>
</div>
</template>
<script>
export default {
// 核心代码
inject: {
value:{
from:'test',
default:()=>{}
}
},
methods:{
changeValue(){
this.value.theme = 'red'
}
}
}
</script>
大体效果如下:
20190407_223227.gif
我们在父组件中提供一个test属性,然后赋值为this,这里之所以赋值this,是为了让provide和inject的绑定变成可响应的,这样,我再子组件中就可以直接改变父组件的theme属性。
6. Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex的功能强大,但应对简单的组件通信用Vuex就显得多余了,有种杀鸡用牛刀的感觉,还会增加我们代码的理解难度。
Vuex作为我们必须掌握的技能之一,这里也不再赘述,不了解的话,官网就是最好的学习材料。
总结
vue中组件通信的方式很多,应对不同情况,灵活地采用最适合的方式,才能使我们的代码变得优雅。
以上是目前为止,我所知的所有通信方式,如有遗漏,欢迎补充。
以下,是代码的demo地址
https://github.com/hanwolfxue/blog-demo-vue-communicate.git