父子组件通信

  1. 父组件通过import的方式导入子组件,并在components属性中注册,然后子组件就可以用标签的形式嵌进父组件了。
//父组件
<template>
<div>
<h4>这里是父组件的地盘<h4>
<Child></Child> //这是自定义的子组件标签
</div>
</template>
<script>
import Child from '../components/Child.vue' //导入子组件
export default{
components:{Child} //注册子组件
}
</script>
//子组件
<template>
<div>
<h4>这里是子组件的地盘</h4>
</div>
</template>
<script>
</script>

通过props实现通信

子组件的props选项能够接收来自父组件的数据,但是props是单向绑定的,即只能父组件向子组件传值,反之则不能。有两种传值方式:

静态传值

子组件通过props选项来声明一个自定义的属性,父组件可以在嵌套子组件标签的时候,通过这个属性向子组件传值

//父组件
<template>
<div>
<h4>这里是父组件的地盘<h4>
<Child message="送给子组件的悄悄话"></Child> //通过子组件自定义的message属性传值
</div>
</template>
<script>
import Child from '../components/Child.vue' //导入子组件
export default{
components:{Child} //注册子组件
}
</script>
//子组件
<template>
<div>
<h4>这里是子组件的地盘</h4>
<h4>{{"显示父组件传过来的值:"+message}}</h4>
</div>
</template>
<script>
props:["message"] //在这里声明一个自定义属性
</script>

动态传值

通过props传入的是一个静态的值,使用v-bind可以实现传递动态数据。通过v-bind绑定props自定义的属性,传递过去的就不是静态的字符串了,它可以是一个表达式、布尔值、对象等任何类型的值。

//父组件
<template>
<div>
<h4>这里是父组件的地盘<h4>
<Child message="送给子组件的悄悄话"></Child> //通过子组件自定义的message属性传值
<Child :message="a+b"></Child> //动态传递表达式
<Child :message="msg"></Child> //用变量进行动态赋值
</div>
</template>
<script>
import Child from '../components/Child.vue' //导入子组件
export default{
data(){
return{
a:'这是一个小傻子',
b:11234,
msg:"这是一个大傻子"+Math.random()
}
}
components:{Child} //注册子组件
}
</script>
//子组件
<template>
<div>
<h4>这里是子组件的地盘</h4>
<h4>{{"显示父组件传过来的值:"+message}}</h4>
</div>
</template>
<script>
props:["message"] //在这里声明一个自定义属性
</script>

通过ref实现通信

  1. $refs是所有ref的集合

  2. 如果ref作用在子组件上,指向的是组件实例,可以理解为对子组件的索引,通过$ref可以获取到在子组件里定义的属性和方法。

  3. 如果ref在普通的DOM元素上使用,引用指向的就是DOM元素,通过$refs可以获取到该DOM的属性集合,轻松访问到DOM元素,作用于JQ选择器类似。

//父组件
<template>
<div>
<h4>这里是父组件的地盘<h4>
<Child ref="msg"></Child> //通过ref实现传值
</div>
</template>
<script>
import Child from '../components/Child.vue' //导入子组件
export default{
components:{Child} //注册子组件
mounted:function(){
console.log(this.$refs.msg)
this.$refs.msg.getMessage("父组件发来贺电!")//可以调用子组件的属性和方法
}
}
</script>
//子组件
<template>
<div>
<h4>这里是子组件的地盘</h4>
<h4>{{message}}</h4>
</div>
</template>
<script>
export default{
data(){
return{
message:""
}
}
getMessage(m){
this.message=m
}
}
</script>
  1. prop和$refs之间的区别:

    • prop着重于数据的传递,它并不能调用子组件里的属性和方法。像创建文章组件时,自定义标题和内容这样的使用场景,最适合使用prop
    • $refs着重于索引,主要用来调用子组件里的属性和方法,其实并不擅长数据传递。而且ref用在dom元素上的时候,能起到选择器的作用,这个功能比作为索引更常用到。

通过$emit实现通信

$emit实现子组件向父组件传值

vm.$emit(event,arg)

$emit 绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件,父组件通过@event监听并接收参数。

//父组件
<template>
<div>
<h4>{{title}}<h4>
<Child @getMessage="showMsg"></Child> //通过$emit实现传值
</div>
</template>
<script>
import Child from '../components/Child.vue' //导入子组件
export default{
components:{Child} //注册子组件
data(){
return{
title:""
}
}
methods:{
showMsg(title){
this.title=title
}
}
}
</script>
//子组件
<template>
<div>
<h4>这里是子组件的地盘</h4>
</div>
</template>
<script>
export default{
mounted:function(){
this.$emit('getMessage',"传值给父组件") //getMessage为自定义事件
}
}
</script>

使用$parent/$children

  • $parent/$children 访问父/子实例
  • $parent 当前组件树的根Vue实例,如果当前组件没有父实例,此实例将会是其自己
  • $children 当前实例的直接子组件。需要注意$children 并不保证顺序,也不是响应式的。如果正在使用$children 来进行数据绑定,考虑使用一个数组配合v-for 来生成子组件,并且使用Array 作为真正的来源
//直接在组件中使用以下方式即可获取子组件和父组件实例
this.$parent
this.$children
this.$children.forEach(child=>{
child.todo()//调用子组件中的属性或方法
})

$children 是一个数组,是直接儿子的集合,关于具体是第几个儿子,那么儿子里面有个_uid 属性,可以知道他是第几个元素,是元素的唯一标识符,根据这个属性,我们可以进行其他的操作

使用provide/inject

下文有讲解

使用$attrs/$listeners

下文有讲解

兄弟通信

非父子组件进行通信的话,需要定义一个公共的实例文件作为中间仓库进行传值,所谓中间仓库就是建立一个事件中心,相当于一个中转站,用来传递事件和接收事件。可以使用一个空的Vue实例作为中央事件总线(Vuex也可以应用于非父子组件间的通信)

发布订阅模式(总线机制/Bus/观察者模式)

运用一:将bus挂载到vue.prototype上

//可以在main.js中配置
Vue.prototype.$bus=new Vue(); //vue原型链挂载总线
//子组件发送数据
this.$bus.$emit("change",data);
//子组件接收数据
this.$bus.$on("change",function(data){})
var Event=new Vue(); //相当于又new了一个vue实例,Event中含有vue的全部方法
Event.$emit('msg',this.msg);//发送数据,第一个参数是发送数据的名称,接收时还用这个名称接收,第二个参数是这个数据现在的位置
Event.$on('msg',function(msg){
//在这里对数据进行操作
}); //接收数据,第一个参数是数据的名字,与发送时的名字对应,第二个参数是一个方法,要对数据的操作

mounted(){        //两种接收的方式
// 定义变量,存储this,防止this指针发生偏转
var _this = this;
Event.$on('a-msg',function(a){
_this.a=a;
});

Event.$on('b-msg',function(b){
this.b = b;
}.bind(this))// 函数上绑定this防止指针偏转
}

运用二:在实际运用中,一般将bus抽离出来,新建一个Bus.js文件

//Bus.js
import Vue from "vue";
const Bus=new Vue()
export default Bus

组件调用时先引入(但这种引入方式,经过webpack打包之后可能会出现Bus局部作用域的情况,即引用的是两个不同的Bus,导致不能正常通信)

//组件one
import Bus from './Bus'
export default {
data(){
return{
......
}
},
methods:{
...
Bus.$emit('msg',222);
}
}
//组件two
import Bus from './Bus'
export default{
data(){
return{
......
}
},
mounted:{
Bus.$on('msg',content=>{
console.log(content)
})
}
}

运用三:直接将Bus注入到Vue根对象

//main.js
import Vue from 'vue'
const Bus=new Vue()
new Vue({
data: { Bus },
render: h => h(App),
}).$mount("#app");
//在子组件中,通过this.$root.Bus.$on()和this.$root.Bus.$emit()来调用

注意

在使用$bus的时候不要忘记在接收bus的组件的beforeDestroy函数中销毁bus,不销毁的话会一直叠加调用这个方法(只要页面没有强制刷新,存在组件切换,bus.$on方法会被多次绑定,造成事件多次触发):

 //这个方法比较合理,在组件销毁时卸载
beforeDestory(){
this.$bus.$off('msg')//此处以将bus挂载到vue.prototype上的写法为例
}
//或者在每次绑定前先解绑事件(bus.$off('事件名称'))
this.$bus.$off('msg')
this.$bus.$on('msg',params)

//上述代码也可以写为
this.$bus.$off('msg').$on('msg',params)

另外,在vue组件中,也可使用$on ,$once 去监听所有的钩子函数,比如上述beforeDestory 方法也可以写成

this.$once('hook:beforeDestroy',()=>{
this.$bus.$off('msg')
})

这样就可以把销毁函数写在绑定函数的下面,增强代码可读性

使用Vuex

参考Vuex 教程

跨级通信

使用Bus、Vuex

使用$attrs/$listeners

多级组件嵌套需要传递数据时,通常使用的方法时通过vuex 。但是如果仅仅是传递数据,而不做中间处理,使用vuex 处理,未免有些大材小用,为此Vue2.4版本提供了另一种方法:$attrs/$listeners

  • $attrs 包含了父作用域中不被prop 所识别且获取的特性绑定(classstyle 除外)。当一个组件没有声明任何prop 时,这里会包含所有父作用域的绑定(classstyle 除外),并且可以通过v-bind='$attrs' 传入内部组件。通常配合inheritAttrs 选项一起使用
  • $listeners 包含了父作用域中的(不含.native 修饰器的)v-on 事件监听器。它可以通过v-on='$listeners' 传入内部组件
//Parent.vue
<template>
<div>
<Child
text="直接传字符串"
:text1="message"
:child2="msg"
:child3="msg"
@message="alertMessage"
@msg="todo"
v-on="$listeners"
></Child>
<!-- 上面的v-on="$listeners",把message和msg传给子组件 -->
</div>
</template>
<script>
import Child from "./Child";
export default {
data() {
return {
msg: "Get out of my face" + Math.random(),
message: "这是一个动态的消息" + Math.random(),
};
},
components: {
Child,
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
methods: {
todo() {
console.log("this is Parent");
},
},
};
</script>
//Child.vue
<template>
<div>
<h5>$attrs:{{ $attrs }}</h5>
<p>text:{{ text }}</p> <!--直接显示父组件传入的被prop识别的属性-->
<p>child2:{{ $attrs.child2 }}</p> <!--显示父组件传入的没有被prop识别的属性-->
<p>child3:{{ $attrs.child3 }}</p>
<Child2 v-bind="$attrs" @dd="dd" v-on="$listeners" child4="uu"></Child2>
</div>
</template>
<script>
import Child2 from "./Child2";
export default {
name: "Child",
props: ["text", "text1"], //父组件传递给子组件的值通过props接收
components: { Child2 },
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
data() {
return {};
},
methods: {
dd() {
console.log("this is Child");
},
},
created() {
console.log("###########");
console.log(this.$attrs);//没有被prop识别的属性有:child2、child3
console.log(this.$listeners);//没有使用.native修饰符的事件有:message、msg
this.$listeners.msg(); //调用了父组件传来的msg()方法,控制台打印this is Parent
},
};
</script>
//Child2.vue
<template>
<div>
<h5>$attrs:{{ $attrs }}</h5>
<p>child2:{{ child2 }}</p>
<p>child3:{{ $attrs.child3 }}</p> <!--跨级显示祖组件传入的没有被prop识别的属性-->
<p>child4:{{ $attrs.child4 }}</p>
</div>
</template>
<script>
export default {
name: "Child-2",
props: ["child2"], //父组件传递给子组件的值通过props接收
created() {
console.log("###########");
console.log(this.$attrs);//没有被prop识别的属性有:child4、child3
console.log(this.$listeners);//没有使用.native修饰符的事件有:message、msg、dd
this.$listeners.dd();
this.$listeners.msg();//跨级调用方法
},
};
</script>

使用provide/inject

这对选项要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终有效.即,祖先组件中通过provide 来提供变量,然后在子孙组件中通过inject 来注入变量。provide/inject 主要解决跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系

假设:parent.vue是child2.vue的上级组件

//parent.vue
export default {
provide: {
name: '来自祖组件的问候'
}
}
// child2.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // 控制台打印:来自祖组件的问候
}
}

provide与inject如何实现数据响应

  • provide 提供祖先组件的实例,然后在子组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西,比如props、methods

  • 使用2.6最新API Vue.observable 优化响应式provide (推荐)

栗子:

孙组件D、E和F获取A组件传递过来的color值,并能实现数据响应式变化,即A组件的color变化后,组件D、E、F会跟着变

// A 组件 
<template>
<div>
<h1>A 组件</h1>
<button @click="() => changeColor()">改变color</button>
<ChildrenB />
<ChildrenC />
</div>
</template>
<script>
export default {
data() {
return {
color: "blue"
};
},
// provide() {
// return {
// theme: {
// color: this.color //这种方式绑定的数据并不是可响应的
// } // 即A组件的color变化后,组件D、E、F不会跟着变
// };
// },
provide() {
return {
theme: this//方法一:提供祖先组件的实例
};
},
methods: {
changeColor(color) {
if (color) {
this.color = color;
} else {
this.color = this.color === "blue" ? "red" : "blue";
}
}
}
// 方法二:使用2.6最新API Vue.observable 优化响应式 provide
// provide() {
// this.theme = Vue.observable({
// color: "blue"
// });
// return {
// theme: this.theme
// };
// },
// methods: {
// changeColor(color) {
// if (color) {
// this.theme.color = color;
// } else {
// this.theme.color = this.theme.color === "blue" ? "red" : "blue";
// }
// }
// }
}
</script>
// F 组件 
<template functional>
<div class="border2">
<h3 :style="{ color: injections.theme.color }">F 组件</h3>
</div>
</template>
<script>
export default {
inject: {
theme: {
//函数式组件取值不一样
default: () => ({})
}
}
};
</script>