【Vue.js】Vue3父子组件数据通信案例(基础案例)
概览
- 前言
- 父子通信流程
- 关键技术点
- 关键规则
- 父→子:子用difineProps 接收
- 1. 在父组件中注册子组件
- 2. 子组件接收父组件传入的数据
- 3. 补充与总结
- 子→父 emit 通知父组件更新
- 案例1:简单 子→父 更新值
- 案例2 :子→父 form表单更新
前言
在 Vue 3 中,父组件向子组件传递数据是通过props
实现的。父组件在子组件的标签上绑定数据,子组件通过定义props
接收这些数据。这种方式使得数据单向流动,确保组件之间的数据传递清晰且易于管理。接下来,我们将详细探讨如何通过props
实现父组件到子组件的数据传递。
本博客文章内容均可以在 Vue演练场中直接使用.
- 强烈建议和我一样的初学Vue者在演练场中运行代码。
父子通信流程
父子组件数据传递流程如图:
关键技术点
- 数据绑定:父组件用
v-bind:
(或 : 缩写)传递javaScript 的变量/函数/基本类型数据; - Prop 声明:子组件通过
defineProps
定义接收规范 - 单向数据流:子组件不能直接修改 props(需通过 emit 通知父组件修改)
关键规则
- 数据入口:父组件通过 props 传递数据(唯一入口)
- 通信出口:子组件通过 emit 触发事件(唯一出口)
- 数据所有权:谁持有数据,谁负责修改(父组件管理状态)
- 性能优化:避免在子组件中解构 props(用 toRefs 保持响应性)
父→子:子用difineProps 接收
简单来说:
- 父组件传数据给子组件,父组件需要注册子组件,并在子组件标签中,用
v-bind
绑定父组件数据到子组件 - 子组件接收父组件数据,需要定义
difineProps
数组接收父组件。
强烈建议以下代码直接在Vue.js 的演练平台中运行
父组件直接在演练台现有的App.vue中编写即可,子组件直接新建一个Child.vue,在App.vue中直接使用import Child from './Child.vue'
即可实现注册,非常方便,下面同。
1. 在父组件中注册子组件
1.父组件引入子组件页面。并在模板中使用。
在父组件页面index.vue
先把子组件,例如名为Child.vue
的页面注册,并在index.vue中,使用子组件 ,也就是说,在父组件index.vue
模板中,有子组件标签,例如 <Chlid>
等等。
2. 在父组件引入的子组件标签中,通过属性绑定,传入父组件想要给子组件的数据
在下面样例中,父组件想要传递给子组件的值,都是通过在引入的子组件标签中,直接把
- 父组件通过属性
send-car
来传递index.vue中的变量 car ,给子组件; - 父组件通过属性
gift-list
来传递index.vue中的响应式数组gifts 给子组件。
index.vue (父页面)
<template>
<div><h3>父组件</h3><h5>父亲的车: {{car}}</h5><h5>父亲准备送给孩子的礼物</h5><ul><li v-for="(gift,index) in gifts" :key="index">{{gift}}</li></ul><Child :send-car="car" :gift-list="gifts"></Child>
</div>
</template>
<script setup>
import { ref,reactive } from 'vue'
// 引入子组件
import Child from './Child.vue'
// 父组件本身的变量
const car= ref('奔驰S450')
const gifts=reactive(['500元红包','乒乓球','帽子']);
</script>
解释
-
在上面的代码中:
:send-car
的作用是将父组件中定义的响应式变量 car 数据绑定到子组件的sendCar
属性上。使用:
表示这是一个动态绑定,Vue 会将 car 的值传递给子组件。 -
在
:send-car="car"
,等号左边的属性,是子组件若要接收时,需要声明的变量,子组件需要通过 defineProps 定义sendCar
属性接收(因为 Vue 会自动转换 kebab-case 为 camelCase);等号右边的值,正是父组件index.vue中声明的变量。
2. 子组件接收父组件传入的数据
子组件通过difineProps 接收父组件传过来的数据
- 子组件
Child.vue
, 通过定义defineProps
数组,并且在defineProps
属性中声明父组件传过来的
数据,子组件直接获取即可。注意,父组件的属性绑定中,声明的send-car ,子组件需要转为camelCase
才能接收。 - 如果你想直接获取父组件传过来的值,拿来直接渲染到子组件模板中(即不需要其余逻辑),直接在模板中使用插值语法(也就是
{{}}
)拿到。 - 如果你需要存储父组件传过来的数值,并进行后期的逻辑操作,那么你就需要从 Vue 的库中导入一个名为
defineProps
的函数
Child.vue
- 【情况1】子组件直接拿到父组件的数据,渲染到页面上,不做任何处理
<template><div><h3>子组件</h3><h6>儿子收到父亲的汽车:{{sendCar}}</h6><h5>儿子收到父亲的礼物</h5><ul><li v-for="(gift,index) in giftList" :key="index">{{gift}}</li></ul></div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
// 只想把父组件的值,拿来就用,不做其他逻辑处理
defineProps(['sendCar','giftList'])
</script>
- 【情况2】子组件存储父组件传过来的值,进行后续的逻辑校验与判断
<template>
<!--.... 和上面一样-->
</template>
<script setup lang="ts">
// 引入 defineProps
import { defineProps } from 'vue';
import {ref} from 'vue'
// defineProps(['sendCar','giftList'])
// 定义 props
const props = defineProps({sendCar: String,giftList: Array
});
const gg=ref(props.giftList)
console.log(gg.value)
</script>
- 【情况3:对传输数据校验】
如果你还想对父组件传过来的值进行校验或者赋默认值等操作,可以扩充props内容
<!-- 子组件 Child.vue -->
<script setup>
const props = defineProps({title: String, // 类型校验dataList: { // 复杂校验type: Array, // 传递的数据类型是数组required: true, //default: () => []}
});
console.log(props.title); // 使用数据
</script>
两种写法的说明
// 数组写法(仅声明属性名,无类型校验)
defineProps(['sendCar', 'giftList'])// 对象写法(可类型校验+默认值)
defineProps({sendCar: { type: String, required: true },giftList: { type: Array, default: () => [] }
})
3. 补充与总结
在父组件中,根据传递的数据类型解析方式,来确定是否使用 :
修饰符表示:
绑定方式 | 示例 | 传递的数据类型 |
---|---|---|
带 : 动态绑定 | :prop=“value” | 可以是任意类型(变量、对象、数组、函数等) |
不带:静态属性 | prop=“val” | 只能是字面意义上的字符串(即使内容是 “true” 或 “[1,2,3]” 也会被当作字符串) |
只要用 :
绑定,我们可以传递:
- 基本类型:String、Number、Boolean、null、undefined
- 引用类型:Array、Object、Function、Date 等
- 响应式数据:ref、reactive 包装的对象
- 函数,函数传递必须强制使用
:
,例如<Child :onClick="handleClick" />
注意: 即使传递的是字面量(非变量),也需要 :
来正确解析类型:例如
<!-- 需要 : 才能传递正确的类型 -->
<Child :count="42" :disabled="false" />
<!-- 否则会变成字符串 -->
<Child count="42" disabled="false" /> <!-- 实际传递的是 "42" 和 "false" -->
注意:
如果父组件传递给子组件的是Object 类型的数据,例如对象数组,那么直接打印只会显示[Object]
在 Vue 3 中,props 被包装成 Proxy 对象,控制台默认不会自动展开其内部数据,而是显示 Object 或 Proxy。用 JSON.stringify() 查看原始数据。
总结:父组件需要声明为小写,中间用 -
隔开的形式,子组件需要在defineProps 中转成camelCase才能接收成功。
子→父 emit 通知父组件更新
案例1:简单 子→父 更新值
当子组件准备向父组件传递数据的时候,子组件是主动方,所以子组件中可以设定在任何时间任何地方通知父组件更新数据。
1. 定义事件及发送时机与方式
(1)首先,子组件需要声明触发事件 const emit =defineEmits(['getGift'])
Child.vue(子组件)*
<template><div><h3>子组件</h3><h4>玩具:{{toy}}</h4></div>
</template>
<script setup lang="ts">
// 声明事件
const emit =defineEmits(['getGift'])
import {ref,onMounted} from 'vue'
const toy=ref('小熊玩偶')
onMounted( ()=>{
setTimeout(()=>{emit('getGift',666);
},4000)
})
</script>
2. 定义父组件接收子组件传送消息的方法
既然子组件要发送数据给父组件,父组件需要对子组件传过来的消息,进行一定的处理。
- 在父组件中,你可以通过
v-on
或@
来监听子组件触发的事件,并处理事件逻辑。
所以,在@get-gift=getGifts
中,被@
修饰的方法名,是子组件定义发送的事件名。等号右边的函数,是父组件处理子组件数据的方法。这个方法定义在父组件中。 - 也就是说:@get-gift 的含义是监听子组件触发的名为
get-gift
的事件,并在事件发生时执行父组件中定义的 getGifts 方法。
index.vue(父组件)
<template>
<div><h3>父组件</h3><h5>父组件收到子组件的礼物</h5><Child @get-gift="getGifts"></Child>
</div>
</template>
<script setup lang="ts">
import { ref,reactive } from 'vue'
// 引入子组件
import Child from './Child.vue'
// 父组件本身的变量
function getGifts(value:number){console.log("收到礼物...."+value)
}
</script>
上述父子组件实现的效果是,子组件会在3s后触发事件,并携带参数666。父组件监听子组件的 getGifts
事件,当子组件事件触发时,会执行父组件与之绑定的事件getGifts方法,同时子组件会隐式的把参数传入到父组件中getGifts方法中。父组件需要提前定义好参数接收。
案例2 :子→父 form表单更新
父子组件传输:form表单更新
- 子组件使用
$emit
或者defineEmits
触发事件;父组件使用v-on
或@
监听子组件触发的事件监听。也就是说,子组件defineEmits 中字符串名字是什么,那么在父组件中@后面跟的就是什么名字。只不过,子组件是短横线连接的事件名,父组件监听时,事件名转换为驼峰命名。
注意:使用emit
可以传递单个值:emit('size-change', 10);
也可以传递对象:emit('form-submit', { name, email })
,也可以传递表单 emit("update:modelValue", formValue.value);
<!-- 或组合式写法 :推荐写法-->
<script setup>
const emit = defineEmits(['update-data']);
// 更新modelValue的固定写法,直接提示父组件更新表单emit("update:modelValue", formValue.value);
</script>
<!-- 父组件 Parent.vue -->
<Child @update-data="handleUpdate" />
<script setup>
const handleUpdate = (data) => {console.log("子组件传来的数据:", data);
};
</script>
index.vue
<template><div><h3>父组件</h3><h5>父组件收到子组件的表单数据:</h5><p>用户输入的内容:{{ formData }}</p><Child @update-form="handleFormUpdate" /></div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue';
// 父组件的响应式数据,用于存储子组件传递的表单数据
const formData = ref('');
// 处理子组件触发的表单更新事件
function handleFormUpdate(data: string) {formData.value = data;
}
</script>
父组件:
- 使用 v-model 将 formData 绑定到子组件。
- v-model 是 Vue 的语法糖,它会自动处理子组件触发的 update:modelValue 事件,更新 formData 的值。
Child.vue
<template><div><h3>子组件</h3><form @submit.prevent="submitForm"><label for="userInput">请输入内容:</label><input id="userInput" v-model="userInput" type="text" /><button type="submit">提交</button></form></div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { defineEmits } from 'vue';
// 声明可以触发的事件
const emit = defineEmits(['update-form']);
// 子组件的响应式数据,用于双向绑定表单输入
const userInput = ref('');
// 提交表单时触发事件,将用户输入的数据传递给父组件
function submitForm() { emit('update-form', userInput.value);}
</script>
子组件:
- 使用 v-model 绑定输入框的值到 formValue 。
- 在提交表单时,触发 update:modelValue 事件,将 formValue 的值传递给父组件。
- Vue 会自动处理这个事件,更新父组件中绑定的值。