组件通信
组件之间的通信主要分为三种情况:父子组件之间通信、兄弟组件之间通信、跨级通信
首先,官方文档推荐了Vuex插件,在此就不讲述Vuex的通信方式了,关于Vuex的入门我在上一篇文章Vuex入门(新手向)中讲过,在这里只讲原生Vue的方式。
父子组件之间传递
父组件向子组件通信
方法一:利用props
props在Vue官方文档中的描述就是用于接收来自父组件的数据,在子组件中,如果props声明的名称与子的data一致,会报错,data无法显示。
prop是properties的缩写,就是属性的意思。通过props可以在组件上注册的一些自定义特性,为单向数据流,父级prop的更新会向下流动到子组件中,反过来不行。
props声明的属性(properties)与v-bind绑定的属性(attributes)有什么区别?v-bind.prop修饰符又有什么用?
先说结论:props声明的properties是父组件中v-bind声明的attributes的映射。
properties由DOM定义,而attributes由html定义。一些html的attributes与properties可以一一映射,比如id属性,有些属性无法一一对应,不能把properties和attributes都翻译为属性,不能混淆在一起。attributes会初始化DOM的properties。例如input标签中设置value=“a”,html中input的attributes-value是a,当html被浏览器渲染后,会创建DOM节点,此时properties-value被初始化为a,但是当用户在input中输入b时,其DOM的properties-value就变成了b,而其html的attributes-value依旧是a。input.getAttribute('value')返回a。可以通过v-bind.prop修饰符来让属性绑定到property上。
- 传值
整体来说就是在子组件中设置props指向父组件中绑定的属性,具体方式是在子组件中设置props声明需要从父组件中接收的数据,该数据通过一个特性名来表示。当父组件调用子组件时,在组件标签上v-bind绑定一个属性,该属性名就是子组件props的特性名。
示例代码如下:
//父组件
<template>
<div class="box" id="father">
<!--这里是父组件-->
<Child
:communicate="msg">
<!-- 这里communicate是一个属性名,与子组件声明的props中的名一致;msg的值与父组件的data内容一致,是Vue的双向绑定 -->
</Child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
name: "father",
data (){
return {
msg:'message from father'
}
}
}
</script>
//子组件
<template>
<div id="child">
<!--这里是子组件-->
<!--这里communicate就是props接收的数据-->
{{communicate}}
</div>
</template>
<script>
export default {
name: "child",
data (){
return {
}
},
props:{
//这里communicate就是props声明的特性名,用来获取来自父级的数据
communicate:{
type:String,
required:true
}
}
}
</script>
- 传引用
与props传值方式类似,只不过在prop验证时,props的属性名如果是数组或对象这种引用类型的话,其默认值必须从一个工厂函数获取。
propA: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
propB: {
type: String,
//数据类型的默认值可以直接作为default的value
default:'abc'
required: true
}
方法二:直接访问子组件(子元素)
- 父链 $children
父组件中可以利用this.$children来访问父组件中的所有子组件,并且可以递归下去到子组件的子组件,一直到最内层的组件,像一条链子一样。在接下来的子组件向父组件通信中也可以使用父链的另一个api实现
- 子组件索引 $refs
当子组件较多时,通过$children来访问子组件就比较复杂了,当子组件较多时可以在子组件中设置索引ref="索引名",父组件中利用this.$refs.索引名来访问子组件传递事件。
子组件向父组件通信
方法一:利用v-on
子组件与父组件通信一般就是传递事件回调,Vue中也有类似JS中的观察者模式(addEventListener)的监听模式,具体来说就是在子组件中注册自定义事件$emit('事件名','事件内容'),在父组件中设置事件@事件名,这个事件名里的函数参数就是子组件中声明的事件内容。同时父组件中用$on()来监听。
示例代码如下:
//父组件
<template>
<div class="box" id="father">
<!--这里是父组件-->
<Child
@eventSelf="fn">
<!-- 这里eventSelf是在子组件中自定义的事件名,fn是该事件所调用的方法,在父组件的methods中声明 -->
</Child>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: {
Child
},
methods: {
fn (val) {
console.log(val);
}
}
}
</script>
//子组件
<template>
<div id="child">
<!--这里是子组件-->
<!--这里的click是通过v-on监听的原生button的点击事件,最初触发的就是click,之后才进行通信。handler是click事件的调用方法,在子组件的methods中声明-->
<button @click="handler">Click me!</button>
</div>
</template>
<script>
export default {
name: "child",
data (){
return {
}
},
props:{
//这里fn就是props声明的特性名,用来获取来自父级的数据
fn:{
type:String,
required:true
}
},
methods:{
handler:function(){
//这里注册自定义事件,事件名命名为eventSelf,具体事件用到的方法是在父组件中声明的。
this.$emit('eventSelf')
}
}
}
</script>
方法二:父链$parent
前面讲父组件向子组件通信时用到了$children的方式,同样,当子组件向父组件通信时,可以使用$parent来无限递归访问,知道根组件为止。
方法三:利用v-model
这种方法其实是方法一的特殊情况,就是子组件注册自定义事件名为input的时候,$emit('input',事件内容),按照方法一的方式,应该在父组件上监听事件@input=“父事件名”,由于v-model是与input双向绑定的一个语法糖,所以可以通过设置v-model=“value”间接来实现父子通信,一般这种方法只用于表单中。
示例代码如下:
//父组件
<template>
<div class="box" id="father">
<!--这里是父组件-->
<p>总数:{{total}}</p>
<Child
v-model="total">
<!-- 这里v-model就是监听input事件以更新数据,就相当于@input,total是监听的数据,也就是input的value值,它的改变将导致data中的total改变,继而影响p标签中绑定的total -->
</Child>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: {
Child
},
data (){
return{
total:0
}
}
methods: {
}
}
</script>
//子组件
<template>
<div id="child">
<!--这里是子组件-->
<!--这里的click是通过v-on监听的原生button的点击事件,最初触发的就是click,之后才进行通信。handler是click事件的调用方法,在子组件的methods中声明-->
<button @click="handler">Click me!</button>
</div>
</template>
<script>
export default {
name: "child",
data (){
return {
counter:0
}
},
methods:{
handler:function(){
this.counter++;
//这里注册自定义事件,事件名命名为input,input的value就是counter。
this.$emit('input',this.counter)
}
}
}
</script>
兄弟组件通信
通过上面讲述的父子组件的通信,可以容易想到兄弟组件A、B之间的通信可以通过一个共同父组件C或者共同子组件D来作为中介进行A到C/D,C/D再到B之间中转通信。
在Vue.js 2.x中,可以使用一个空的Vue实例作为通信所需的中介组件,这个空的Vue实例就叫做中央事件总线(bus)。具体实施方式就是首先创建一个空Vue实例(bus),里面可以没有任何内容,利用bus.emit作为中介来中转两个组件之间的通信。
示例代码如下:
//一般会把bus组件抽离出来放在main.js或者App.vue中
import Vue from 'vue'
const Bus = new Vue()
export default Bus
组件调用时引入
<template>
<div id="child">
<!--这里是组件A-->
<button @click="handler">传递事件</button>
</div>
</template>
...
import Bus from './Bus'
export default {
data() {
return {
.........
}
},
methods: {
handler:function(){
Bus.$emit('事件名1', ‘来自组件A的消息’)
}
},
}
<template>
<div id="child">
<!--这里是组件B-->
<p>{{content}}</p>
</div>
</template>
...
import Bus from './Bus'
export default {
data() {
return {
.........
}
},
//注意这里在mounted中声明,在dom渲染前加载。
mounted(){
var _this = this;
Bus.$on('事件名1',msg =>(
_this.content = msg
))
}
}
跨级组件通信
方法一:父链$parent、$children
这种方式在前面已有所讲述,就是逐级递归,一层一层的传递
方法二:如果只跨了两级,也就是爷孙组件,可以将子组件作为一个中介传递。
方法三:provide / inject
这个api是Vue2.2新增的,可以在祖先组件中通过provider来提供变量,然后在后代组件中通过inject来注入变量。