Vue3中的常见组件通信之props和自定义事件

时间:2024-06-03 07:39:19

Vue3中的常见组件通信

概述

​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refsparent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。

组件关系 传递方式
父传子 1. props
2. v-model
3. $refs
4. 默认插槽、具名插槽
子传父 1. props
2. 自定义事件
3. v-model
4. $parent
5. 作用域插槽
祖传孙、孙传祖 1. $attrs
2. provide、inject
兄弟间、任意组件间 1. mitt
2. pinia

​ 本文讲述props和自定义事件,其他的通信方式后面再更新。

1.props

1.1 准备父子组件

首先准备一个简单的父子组件的样式,如下是父组件代码:

<template>
  <div class="father">
    <h2>这是父组件</h2>
	<Child/>
  </div>
</template>

<script setup lang="ts" name="Father">
	//引入子组件
	import Child from './Child.vue';
	
</script>

<style scoped>
	.father{
		margin: 5px;
		background-color:rgb(205, 165, 32);
		padding: 20px;
		color: white;
	}
</style>

以下是子组件代码:

<template>
  <div class="child">
    <h2>这是子组件</h2>
  </div>
</template>

<script setup lang="ts" name="Child">

</script>

<style scoped>
	.child{
		margin: 5px;
		background-color: rgba(93, 135, 20, 0.224);
		border: 1px solid;
		border-color: white;
		box-shadow: 0 0 5px;
		padding: 10px;
		color: #000;
	}	
</style>

运行结果如下所示:

image-20240601203254108

1.2 父传子通信的实现

用props实现父传子通信,可以用标签中的属性值直接传递数据,父组件代码中只需要在子组件标签上添加属性值即可,如下代码,传递两个数据:

<Child :f2CMsg="f2CMsg" :f2CObj="f2CObj"/>

定义传递的数据内容,两个数据分别是ref定义的基本类型的响应式数据和reactive定义的对象类型的响应式数据。

//引入ref和reactive
import {ref,reactive} from 'vue'

//数据,ref定义的基本类型的响应式数据
let f2CMsg =ref('你好,子组件。')

//数据,reactive定义的对象类型的响应式数据
let f2CObj = reactive({
id:"asdfg01",
name:"张三",
age:18,
detail:"这是父组件中的对象信息"
})

在子组件中需要声明接收数据props,注意接收的数据格式是数组,哪怕是只传递一个数据也要写成数组的形式。

//声明接收数据props
defineProps(['f2CMsg','f2CObj'])

子组件中在页面展示接收的数据,如下代码:

<h4>父组件传递的信息:{{ f2CMsg }}</h4>
<h4>父组件传递的对象:</h4>
<ul>
    <li>姓名:{{f2CObj.name}}</li>
    <li>年龄:{{f2CObj.age}}</li>
    <li>详情:{{f2CObj.detail}}</li>
</ul>

然后运行结果如下:

image-20240601211035944

1.3 子传父通信的实现

用props实现子传父的通信需要父组件先向子组件传递一个函数,然后传递的数据以参数的形式传递给函数,父组件中先定义子传父的数据变量和函数或者方法如下:

//定义子传父的数据变量
let c2FMsg = ref('')
//定义方法传递给子组件
function getMsg(value:string){
    c2FMsg.value = value
}

在标签中把方法传给子组件:

<Child :f2CMsg="f2CMsg" :f2CObj="f2CObj" :sendMsg="getMsg"/>

在子组件中接收数据,并定义数据内容:

defineProps(['f2CMsg','f2CObj','sendMsg'])
let c2FMsg = ref('你好,父组件。')

在子组件中设置一个按钮,给按钮绑定点击事件,点击事件触发sendMsg方法,并传递参数:

<button @click="sendMsg(c2FMsg)">点我向父组件传递信息</button>

现在数据应该已经传递给了父组件,在父组件可以用如下代码展示:

<h4 v-show="c2FMsg">子组件传递的信息:{{ c2FMsg }}</h4>

运行结果如下:

image-20240601213552916

点击按钮后运行结果如下:

image-20240601213621311

至此我们已经用props实现了子传父的通信功能。当然子传父也可以传递对象。此处不再展示。如下是完整代码:

父组件中的代码:

<template>
  <div class="father">
    <h2>这是父组件</h2>
	<h4 v-show="c2FMsg">子组件传递的信息:{{ c2FMsg }}</h4>	
	<Child :f2CMsg="f2CMsg" :f2CObj="f2CObj" :sendMsg="getMsg"/>	
  </div>
</template>

<script setup lang="ts" name="Father">
	//引入子组件
	import Child from './Child.vue';
	//引入ref和reactive
	import {ref,reactive} from 'vue'
	
	//数据,ref定义的基本类型的响应式数据
	let f2CMsg =ref('你好,子组件。')

	//数据,reactive定义的对象类型的响应式数据
	let f2CObj = reactive({
		id:"asdfg01",
		name:"张三",
		age:18,
		detail:"这是父组件中的对象信息"
	})
	
	//定义子传父的数据变量
	let c2FMsg = ref('')
	
	//定义方法传递给子组件
	function getMsg(value:string){
		c2FMsg.value = value		
	}
</script>

<style scoped>
	.father{
		margin: 5px;
		background-color:rgb(205, 165, 32);
		padding: 20px;
		color: white;
	}
</style>

子组件的代码:

<template>
  <div class="child">
    <h2>这是子组件</h2>
	<h4>父组件传递的信息:{{ f2CMsg }}</h4>
	<h4>父组件传递的对象:</h4>
	<ul>
		<li>姓名:{{f2CObj.name}}</li>
		<li>年龄:{{f2CObj.age}}</li>
		<li>详情:{{f2CObj.detail}}</li>
	</ul>
	<button @click="sendMsg(c2FMsg )">点我向父组件传递信息</button>
  </div>
</template>

<script setup lang="ts" name="Child">
import { ref,reactive } from 'vue';

//声明接收数据props
defineProps(['f2CMsg','f2CObj','sendMsg'])

let c2FMsg = ref('你好,父组件。')

</script>

<style scoped>
	.child{
		margin: 5px;
		background-color: rgba(93, 135, 20, 0.224);
		border: 1px solid;
		border-color: white;
		box-shadow: 0 0 5px;
		padding: 10px;
		color: #000;
	}	
</style>

1.4 小结

用props实现父子通信的步骤是这样的:

父传子:父组件中定义传递的数据–>标签中用属性值直接传递数据–>子组件中声明接收数据–>用插值语法展示数据。

子传父: 父组件先定义接收数据的变量–>父组件中定义函数–>将函数传递给子组件–>子组件中声明接收数据,接收的数据为函数–>子组件中定义传递的数据–>子组件中调用接收的函数,将定义的要传递的数据作为参数进行传递–>父组件中收到数据,可以在页面中展示。

最后总结如下:

父传子:标签中的属性值是非函数

子传父:标签中的属性值是函数

2. 自定义事件

自定义事件通常用于子传父,需要注意在原生事件中事件名是特定的,比如click,keyup等,在自定义事件中事件名是任意的;在原生事件中事件对象 e v e n t 是包含事件相关信息的对象( ‘ p a g e X ‘ 、 ‘ p a g e Y ‘ 、 ‘ t a r g e t ‘ 、 ‘ k e y C o d e ‘ ),在定义事件中事件对象 event是包含事件相关信息的对象(`pageX`、`pageY`、`target`、`keyCode`),在定义事件中事件对象 event是包含事件相关信息的对象(pageXpageYtargetkeyCode),在定义事件中事件对象event是调用emit所提供的数据,可以是任意类型。

2.1 准备父子组件

父子组件代码与样式与本文中1.1中的完全相同,此处不再赘述。

2.2 自定义事件实现子传父通信

首先在子组件中定义要传递的数据,此次依然已字符串为例,如下代码:

let c2FMsg = ref('你好,父组件。')

然后在父组件中定义接收的数据变量,并声明函数,用来保存接收的数据。

let c2FMsg = ref('')

//声明函数saveMsg,用来保存接收的数据
function saveMsg(value:string){
    c2FMsg.value = value
}

之后给子组件绑定自定义事件,事件名为send-message,同时将函数saveMsg传进去,注意自定义事件名的命名规范官方建议采用肉串形式的命名方式。

<!-- 给子组件绑定自定义事件 -->
<Child @send-message="saveMsg"/>

在子组件中需要声明事件

//声明事件
let emit = defineEmits(['send-message'])

触发事件的代码如下:

emit('send-message')

只要在子组件中写出上面触发事件的代码,就可以实现子传父的通信,这次以子组件挂载3秒后自动触发事件为例,如下代码:

//组件挂载3秒后触发事件
onMounted(()=>{
	setTimeout(()=>{
		//触发事件send-message,并传c2FMsg
		emit('send-message',c2FMsg)
	},3000)
})

此时已经实现了子传父的通信,最后在父组件中展示出来,如下代码:

<h3 v-show="c2FMsg">子组件传递的信息:{{ c2FMsg }}</h3>

最后运行页面效果如下,在刚启动页面是如下效果:

image-20240602105624203

3秒钟之后看下的效果如下图所示:

image-20240602105712057

以下是完整代码:

父组件代码:

<template>
	<div class="father">
	  <h2>这是父组件</h2>
	  <h3 v-show="c2FMsg">子组件传递的信息:{{ c2FMsg }}</h3>
	  <!-- 给子组件绑定自定义事件 -->
	  <Child @send-message="saveMsg"/>
	</div>
  </template>
  
  <script setup lang="ts" name="Father">
	  //引入子组件
	  import Child from './Child.vue';
	  import { ref } from 'vue';

	  let c2FMsg = ref('')
  		
  	//声明函数,用来保存接收的数据
  	function saveMsg(value:string){
		c2FMsg.value = value
	}
	  
  </script>
  
  <style scoped>
	  .father{
		  margin: 5px;
		  background-color:rgb(205, 165, 32);
		  padding: 20px;
		  color: white;
	  }
  </style>

以下是子组件代码:

<template>
	<div class="child">
	  <h2>这是子组件</h2>
	</div>
  </template>
  
  <script setup lang="ts" name="Child">
	
	import {ref,onMounted} from 'vue'

	let c2FMsg = ref('你好,父组件。')
  	//声明事件
	let emit = defineEmits(['send-message'])

	//组件挂载3秒后触发事件
	onMounted(()=>{
		setTimeout(()=>{
			//触发事件send-message,并传c2FMsg
			emit('send-message',c2FMsg)
		},3000)
	})
  
  </script>
  
  <style scoped>
	  .child{
		  margin: 5px;
		  background-color: rgba(93, 135, 20, 0.224);
		  border: 1px solid;
		  border-color: white;
		  box-shadow: 0 0 5px;
		  padding: 10px;
		  color: #000;
	  }	
  </style>

2.3 小结

以上便是自定义事件的基本用法,在实际开发中是比较常用的用来实现子传父的通信方式。