Vue组件间通信

164 阅读4分钟

组件间通信的概念

  • 我们先了解组件间通信的概念,先将其拆分为组件和通信这两个词。
    • 组件是vue最强大的功能之一,指的是vue中每一个.vue文件
    • 通信指的是发送者通过某种手段、某种格式来将信息数据传递到收信者手上
  • 组件间通信其本质是实现组件间的信息同步,共享数据回到vue中。
  • vue中每一个组件之间都有各自独立的作用域,但如果仅仅只是完成各自部分的内容,而不实现数据共享的,这样只会让我们的工作更加复杂冗余。所以,组件间通信的目的就是让组件间能够实现通讯,实现数据之间的共享。

组件间通信的方案

  1. 通过props传递
  2. 通过$emit触发自定义事件
  3. 使用ref
  4. 通过EventBus时间全局总线
  5. parent或root
  6. attrs与listeners
  7. Provide与inject
  8. Vuex

props传递数据

  • 适用场景:父组件传递数据给子组件
  • 使用方法:
    • 子组件设置props属性,props可以是数组或者对象,接收父组件传递过来的参数
    • 父组件定义参数,并给指定子组件传递参数
  • 方法示例:
<ChildComponent literal="1" :mes="mes" :todo="todo"></ChildComponent>
props:['mes','todo','literal']
  • 完整代码:
<!-- 父组件 -->
<template>
    <div>
      <!-- 组件传递的数据可以是字面量(写死),也可以是来自父元素的动态数据
            如果父元素为动态数据,要用v-bind动态绑定props的值
            需要将一个对象的所有属性传递时,可以直接通过prop传递,在子组件也可以进行使用
            当父组件数据变化时,也会传递给子组件
      -->
      <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可以接收多个数据
    props:['mes','literal','todo'],
}
</script>

$emit触发自定义事件

  • 适用场景:子组件传递或改变数据给父组件
  • 使用方法:
    • 在子组件中调用$emit函数
    • 在父组件中,当子组件调用$emit函数时,Vue.js会向其父组件发送一个事件,父组件通过v-on指令监听该事件
  • 方法示例:
// 子组件:调用this.$emit()
this.$emit(eventName,payload)
$emit函数传递两个参数 ,第一个参数为事件的名称,字符串类型,第二个参数为可选参数,可以是任何类型的数据

// 父组件:v-on或@事件监听
@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方法来移除事件监听,避免内存泄露
  • 方法示例:
// 第一种初始化方法:新建一个js文件,并将实例对象暴露
// event-bus.js
import Vue from 'vue';
export default new Vue()
//发送消息组件
import EventBus from '../event-bus'
EventBus.$emit('事件名',参数) 
//接收消息组件
import EventBus from '../event-bus'
EventBus.$on('事件名',函数)

// 第二种初始化方法 
// main.js 
import Vue from 'vue'
Vue.prototype.$EventBus=new Vue()
//发送消息组件
this.$EventBus.$emit('事件名',参数) 
//接收消息组件
this.$EventBus.$on('事件名',函数)

// 两种初始化的方法不同之处:
// 当拎出来单独放在一个新的js文件时,需要进行引入,并直接将导出的模块进行触发和监听事件
// 放在main.js里面时,可直接在组件内通过this.$EventBus进行触发和监听事件

// 移除EventBus事件监听
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,也可以通过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使用数据
   {{ 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使用数据 -->
    {{ 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接受一个对象或函数,当需要使用组件间实例this时,需要将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接受一个对象或函数,当需要使用组件间实例this时,需要将provide变成函数形式
    // provide:{
    //   mess:'祖先字面数据'
    // },
    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))