组件间通信的概念
- 我们先了解组件间通信的概念,先将其拆分为组件和通信这两个词。
- 组件是vue最强大的功能之一,指的是vue中每一个.vue文件
- 通信指的是发送者通过某种手段、某种格式来将信息数据传递到收信者手上
- 组件间通信其本质是实现组件间的信息同步,共享数据回到vue中。
- vue中每一个组件之间都有各自独立的作用域,但如果仅仅只是完成各自部分的内容,而不实现数据共享的,这样只会让我们的工作更加复杂冗余。所以,组件间通信的目的就是让组件间能够实现通讯,实现数据之间的共享。
组件间通信的方案
- 通过props传递
- 通过$emit触发自定义事件
- 使用ref
- 通过EventBus时间全局总线
- parent或root
- attrs与listeners
- Provide与inject
- Vuex
props传递数据
- 适用场景:父组件传递数据给子组件
- 使用方法:
- 子组件设置props属性,props可以是数组或者对象,接收父组件传递过来的参数
- 父组件定义参数,并给指定子组件传递参数
- 方法示例:
<ChildComponent literal="1" :mes="mes" :todo="todo"></ChildComponent>
props:['mes','todo','literal']
<!-- 父组件 -->
<template>
<div>
<ChildComponent literal="1" :mes="mes" :todo="todo"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from './Child';
export default {
name:'HomeComponent',
components:{
ChildComponent
},
data() {
return {
mes:'这是home组件信息',
todo:{
text:'内容',
date:'时间'
}
};
},
};
</script>
<!-- 子组件 -->
<template>
<div>
这是孩子里面的{{ mes }}
<br/>
这里是字面量{{ literal }}
<br/>
这里是todo里的text{{ todo.text}}
<br/>
这里是todo里的date{{ todo.date }}
</div>
</template>
<script>
export default {
name:'ChildComponent',
props:['mes','literal','todo'],
}
</script>
$emit触发自定义事件
- 适用场景:子组件传递或改变数据给父组件
- 使用方法:
- 在子组件中调用$emit函数
- 在父组件中,当子组件调用$emit函数时,Vue.js会向其父组件发送一个事件,父组件通过v-on指令监听该事件
- 方法示例:
this.$emit(eventName,payload)
$emit函数传递两个参数 ,第一个参数为事件的名称,字符串类型,第二个参数为可选参数,可以是任何类型的数据
@handleEvent="handleMes"
<!-- 父组件 -->
<template>
<div>
<ChildComponent @handleEvent="handleMes"></ChildComponent>
<p>{{ mes }}</p>
</div>
</template>
<script>
import ChildComponent from './Child';
export default {
name:'HomeComponent',
components:{
ChildComponent
},
data() {
return {
mes:'这是父组件里面的内容',
};
},
methods:{
handleMes(payload){
this.mes=payload
}
}
};
</script>
<!-- 子组件 -->
<template>
<div>
<button @click="handle">点击修改数据</button>
</div>
</template>
<script>
export default {
name:'ChildComponent',
methods:{
handle(){
this.$emit('handleEvent','这是子组件修改后的内容')
}
}
}
</script>
ref
- 适用场景:父组件向子组件传值
- 使用方法:
- 父组件在使用子组件的时候设置ref,父组件在methods里面通过ref调用子组件中的数据
- 子组件展示数据
- 方法示例:
<ChildComponent ref="child"></ChildComponent>
methods:{
handle(){
this.$refs.child.mes='这是父组件传递过来的数据'
}
<p>{{ mes }}</p>
<!-- 父组件 -->
<template>
<div>
<button @click="handle">点击将值传给子组件</button>
<ChildComponent ref="child"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from './Child';
export default {
name:'HomeComponent',
components:{
ChildComponent
},
methods:{
handle(){
this.$refs.child.mes='这是父组件传递过来的数据'
}
}
};
</script>
<!-- 子组件 -->
<template>
<div>
<p>{{ mes }}</p>
</div>
</template>
<script>
export default {
name:'ChildComponent',
data(){
return{
mes:'子组件的数据'
}
},
}
</script>
EventBus
- 适用场景:兄弟组件之间传值
- 使用方法:
- 创建一个EventBus实例(两种初始化方法)
- 一种实例化一个新的Vue对象来创建,放在一个单独的js文件中
- 另一种直接在main.js挂载到prototype上
- 一个组件发送消息,使用$emit方法触发事件
- 一个组件接收消息,使用$on方法监听事件
- 注意:EventBus是一个全局实例,需要避免在多个组件中使用相同的事件名,以免产生冲突。同时,在组件销毁时,需要使用$off方法来移除事件监听,避免内存泄露
- 方法示例:
import Vue from 'vue';
export default new Vue()
import EventBus from '../event-bus'
EventBus.$emit('事件名',参数)
import EventBus from '../event-bus'
EventBus.$on('事件名',函数)
import Vue from 'vue'
Vue.prototype.$EventBus=new Vue()
this.$EventBus.$emit('事件名',参数)
this.$EventBus.$on('事件名',函数)
destroyed(){
EventBus.$off('事件名')
this.$EventBus.$off('事件名')
EventBus.$off()
this.$EventBus.$off()
}
<!-- main.js-->
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
Vue.prototype.$EventBus=new Vue()
new Vue({
render: h => h(App),
}).$mount('#app')
<!-- A组件:发送消息-->
<template>
<div>
<button @click="sendMes">向B组件传值</button>
</div>
</template>
<script>
export default {
name:'ChildComponent',
data(){
return{
mes:'组件A的值'
}
},
methods:{
sendMes(){
this.$EventBus.$emit('send',this.mes)
}
}
}
</script>
<!-- B组件:接收消息 -->
<template>
<div>
{{ mes }}
</div>
</template>
<script>
export default {
name:'HomeComponent',
data(){
return{
mes:'这是B值,准备接收A的值'
}
},
mounted(){
this.$EventBus.$on('send',payload=>{
this.mes=payload
})
}
};
</script>
parent或root
- 适用场景:兄弟组件之间的通信
- 使用方法:
- 通过共同祖辈搭桥
- 一个组件发送消息,通过parent或root使用$emit触发事件
- 一个组件接收消息,通过parent或root使用$on监听事件
- 方法示例:
this.$parent.$emit('事件名','传递的数据')
this.$root.$emit('事件名','传递的数据')
this.$parent.$on('事件名',function})
this.$root.$on('事件名',function})
- 完整代码(与EventBus代码类似,就不再详细写了)
- 注意:父组件访问子组件可以通过refs,也可以通过children,但$children是属于vue2的,它在vue3已经被弃用了
attrs与listeners
- 适用场景:爷父子组件间的通信
- 使用方法:
- 爷组件:在使用父组件的时候 给父组件传递数据
- 父组件:可以通过props接收数据,剩余没有被接收的数据通过attrs存储起来,也可以通过v-bind将值传给子组件
- 子组件:通过props接收数据,没有接收到的数据就会通过this.$attrs接收
- 注意:
- attrs与listeners是组件实例的属性,可以获取到父组件给子组件传递的props数据和自定义事件。
- 当父组件传数据给子组件的时候,如果子组件的props没有进行接收,那数据就会被收集到子组件的attrs里面,如果通过props接收的数据,则不会出现在attrs里面。
- 在子组件上使用v-bind="$attrs"可以直接将值传给当前组件的子组件(也就是孙组件),即使是v-model依旧可以传递。
- 同理,listeners 监听的是自定义事件,通过v-on=$listeners进行监听
- 方法示例:
<HelloWorld :name="name" :age="age" :info="info" @delInfo="delInfo" @updateInfo="updateInfo"></HelloWorld>
<ChildComponent v-bind="$attrs" v-on="$listeners" :height="height" :weight="weight" @addInfo="addInfo"></ChildComponent>
{{ this.$attrs.weight }}
this.$emit('updateInfo')
this.$emit('addInfo')
<!-- 爷组件 -->
<template>
<div id="app">
<HelloWorld :name="name" :age="age" :info="info" @delInfo="delInfo" @updateInfo="updateInfo"></HelloWorld>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
},
data(){
return{
name:'ll',
age:20,
info:{
from:'广州',
job:'教师',
hobby:['打球','吃饭','阅读']
}
}
},
methods:{
delInfo(){
console.log('delInfo');
},
updateInfo(){
console.log('updateInfo');
}
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<ChildComponent v-bind="$attrs" v-on="$listeners" :height="height" :weight="weight" @addInfo="addInfo"></ChildComponent>
{{ name }}
</div>
</template>
<script>
import ChildComponent from './Child';
export default {
name:'HomeComponent',
components:{
ChildComponent
},
props:['name'],
data(){
return{
height:190,
weight:100
}
},
mounted(){
console.log('父组件的',this.$attrs);
console.log('父组件的',this.$listeners);
},
methods:{
addInfo(){
console.log('addInfo');
}
}
};
</script>
<!-- 孙组件-->
<template>
<div>
{{ info.from }}
{{ height }}
{{ this.$attrs.weight }}
</div>
</template>
<script>
export default {
name:'ChildComponent',
props:['info','height'],
mounted(){
this.$emit('updateInfo')
this.$emit('addInfo')
console.log('孙组件的',this.$attrs);
console.log('孙组件的',this.$listeners);
}
}
</script>
Provide与inject
- 适用场景:祖与后代之间的通信
- 使用方法:
- 父组件用provide提供数据
- 后代组件用inject使用数据
- 注意:
- props与$attrs传递数据时,都需要在中间组件中一层层往下传递,而provide与inject不论组件层次有多深,父组件都可以作为其所有子组件的依赖提供者
- provide与inject需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖
- 是非响应式的,如果要子孙组件根据父组件的值进行改变,provide与inject机制不是一个好的选择,需要借助Vuex来管理
- 在vue2中,可以使用Vue.computed让provide具有响应性
- 方法示例:
provide:{
mess:'祖先字面数据'
},
provide(){
return{
mes:this.message,
mess:'字面数据'
}
}
inject:['mes','mess']
<!-- 祖先组件 -->
<template>
<ChildComponent ></ChildComponent>
</template>
<script>
import ChildComponent from './Child';
export default {
name:'HomeComponent',
components:{
ChildComponent
},
data(){
return{
message:'祖先数据'
}
},
provide(){
return{
mes:this.message,
}
},
};
</script>
<!-- 后代组件-->
<template>
<div>
{{ mes }}
{{ mess }}
</div>
</template>
<script>
export default {
name:'ChildComponent',
data(){
return{
}
},
inject:['mes','mess']
}
</script>
Vuex
- 适用场景:
- 在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理;
- 跨组件共享数据、跨页面共享数据;
- 一个组件需要多次派发事件时;
- 需要持久化的数据
- 在大项目中使用vuex,解决了组件之间统一状态的共享问题,实现组件之间的数据持久化。在项目中可以用vuex存放数据,不用每次都要请求后端服务器,这就在保证了数据新鲜度的同时提高了使用性能。
- 由于本文章篇幅过长,详细Vuex实现组件通信内容,请点击👉(Vuex实现组件通信 - 掘金 (juejin.cn))