Vue3+Vite 08:父子组件通信

Vue3+Vite 08:父子组件通信

一、前置准备:创建并引入子组件

在 Vue 中,引入并使用一个组件分为两步:创建子组件文件 → 父组件引入后直接使用。<script setup>语法下,组件引入后自动注册,无需额外配置。

操作步骤

  1. 在项目src/components文件夹下,新建Child.vue文件(子组件)
  2. 在父组件src/App.vue中引入子组件,直接在模板中使用
子组件src/components/Child.vue
<template> <div class="child-box"> <p>我是子组件</p> </div> </template> <script setup> </script> <style scoped> .child-box { border: 1px solid #42b983; padding: 20px; margin: 20px 0; } </style>
父组件src/App.vue
<template> <div style="padding: 20px;"> <h3>我是父组件</h3> <!-- 直接使用子组件标签 --> <Child /> </div> </template> <script setup> // 引入子组件 import Child from './components/Child.vue' </script>

运行后即可看到父子组件的层级关系,后续所有通信示例都基于这个基础结构修改。

二、父传子:defineProps

1. 作用

父组件向子组件传递数据,是最常用的通信方式。子组件通过defineProps声明要接收的数据,父组件通过属性绑定传值。

2. 基础写法

简单场景下,用数组声明要接收的属性名。

子组件代码
<template> <div class="child-box"> <p>子组件接收的标题:{{ title }}</p> <p>子组件接收的数量:{{ num }}</p> </div> </template> <script setup> // 声明要接收的属性 const props = defineProps(['title', 'num']) // 脚本中访问props数据:props.title </script>
父组件代码
<template> <div style="padding: 20px;"> <h3>父组件</h3> <!-- 静态传值:直接写字符串 --> <Child title="静态标题" num="100" /> <!-- 动态传值:用v-bind绑定响应式数据 --> <Child :title="parentTitle" :num="parentNum" /> </div> </template> <script setup> import { ref } from 'vue' import Child from './components/Child.vue' const parentTitle = ref('动态标题') const parentNum = ref(200) </script>

3. 进阶写法

项目中推荐使用对象写法,支持类型校验、默认值、必填设置

<script setup> const props = defineProps({ // 基础类型校验 title: String, // 多类型支持 num: [Number, String], // 必填 + 类型校验 username: { type: String, required: true }, // 带默认值 status: { type: Boolean, default: false } }) </script>

注意:

props只读的,子组件中绝对不能直接修改,否则控制台会报错。如果需要修改,必须通过子传父的方式通知父组件修改。

三、子传父:defineEmits

1. 作用

子组件向父组件发送消息、传递数据。子组件通过defineEmits声明要触发的事件,调用emit触发事件;父组件通过v-on监听事件并接收数据。

2. 代码

子组件代码
<template> <div class="child-box"> <p>子组件</p> <button @click="sendToParent">给父组件发消息</button> </div> </template> <script setup> // 声明要触发的事件 const emit = defineEmits(['sendMsg']) const sendToParent = () => { // 触发事件,第二个参数开始是要传递的数据 emit('sendMsg', '我是子组件传来的数据') } </script>
父组件代码
<template> <div style="padding: 20px;"> <h3>父组件,接收数据:{{ receiveMsg }}</h3> <!-- 监听子组件的sendMsg事件 --> <Child @sendMsg="handleReceive" /> </div> </template> <script setup> import { ref } from 'vue' import Child from './components/Child.vue' const receiveMsg = ref('') // 事件处理函数,参数就是子组件传来的数据 const handleReceive = (data) => { receiveMsg.value = data } </script>

3. 关键说明

  • defineEmits接收数组,里面是所有要触发的事件名
  • 触发事件格式:emit('事件名', 参数1, 参数2...)
  • 事件名推荐使用短横线命名(如send-msg),和原生事件规范保持一致

四、父调用子组件能力:defineExpose

1. 作用

父组件通过ref获取子组件实例,直接调用子组件的方法、访问子组件的数据。适合需要主动触发子组件行为的场景(比如打开子组件弹窗、调用子组件重置方法)。

2. 代码

子组件代码
<template> <div class="child-box"> <p>子组件计数:{{ count }}</p> </div> </template> <script setup> import { ref } from 'vue' const count = ref(0) const addCount = () => { count.value++ } // 主动暴露给父组件的内容,未暴露的内容父组件无法访问 defineExpose({ count, addCount }) </script>
父组件代码
<template> <div style="padding: 20px;"> <h3>父组件</h3> <button @click="callChild">调用子组件方法</button> <!-- 给子组件绑定ref --> <Child ref="childRef" /> </div> </template> <script setup> import { ref, onMounted } from 'vue' import Child from './components/Child.vue' // 创建ref,和模板上的ref属性名一致 const childRef = ref(null) const callChild = () => { // 通过.value访问子组件暴露的内容 childRef.value.addCount() console.log('子组件数据:', childRef.value.count) } onMounted(() => { // 必须在挂载完成后才能获取到子组件实例 console.log('挂载完成,子组件实例:', childRef.value) }) </script>

3. 注意

  • 子组件必须用defineExpose主动暴露内容,父组件才能访问,这是 Vue3 的安全限制
  • 父组件中ref变量在setup同步执行时是null,必须在onMounted及之后的阶段才能访问到实例

五、总结

  1. 父传子:子组件用defineProps声明属性,父组件通过属性绑定传值,单向只读
  2. 子传父:子组件用defineEmits声明事件并触发,父组件用@事件名监听接收
  3. 父调子:子组件用defineExpose暴露能力,父组件通过ref获取实例调用