Vue 3 入门教程6 - 组件通信进阶

一、子组件向父组件通信(子传父)

子组件通过触发自定义事件的方式向父组件传递数据,父组件通过监听该事件接收数据。

1.1 基本实现方式

子组件使用 emit 方法触发自定义事件,并传递数据;父组件在使用子组件时,通过 v-on(简写为 @)监听该事件。

子组件示例
<template>

<div>

<button @click="sendDataToParent">向父组件发送数据</button>

<input type="text" v-model="inputValue" @change="handleInputChange">

</div>

</template>

<script setup>

import { ref, defineEmits } from 'vue'

const inputValue = ref('')

// 定义可以触发的自定义事件,返回一个 emit 函数

const emit = defineEmits(['sendData', 'inputChange'])

// 点击按钮触发事件

const sendDataToParent = () => {

// 触发 sendData 事件,并传递数据(可以传递多个参数)

emit('sendData', '来自子组件的数据', 123)

}

// 输入框内容变化时触发事件

const handleInputChange = () => {

emit('inputChange', inputValue.value)

}

</script>

在 setup 语法中,需要通过 defineEmits 定义组件可以触发的事件,defineEmits 接收一个数组,数组元素为事件名称。返回的 emit 函数用于触发事件,第一个参数是事件名称,后续参数是要传递的数据。

父组件示例
<template>

<div>

<ChildComponent

@sendData="handleSendData"

@inputChange="handleInputChange"

/>

<p>从子组件接收的数据1:{{ childData1 }}</p>

<p>从子组件接收的数据2:{{ childData2 }}</p>

<p>输入框内容:{{ inputContent }}</p>

</div>

</template>

<script setup>

import { ref } from 'vue'

import ChildComponent from './components/ChildComponent.vue'

const childData1 = ref('')

const childData2 = ref('')

const inputContent = ref('')

// 处理 sendData 事件

const handleSendData = (data1, data2) => {

childData1.value = data1

childData2.value = data2

}

// 处理 inputChange 事件

const handleInputChange = (value) => {

inputContent.value = value

}

</script>

父组件通过 @事件名 监听子组件触发的事件,事件处理函数的参数对应子组件传递的数据。

1.2 事件验证

可以在 defineEmits 中对事件传递的数据进行验证,确保数据类型符合预期。

<script setup>

const emit = defineEmits({

// 验证事件传递的数据类型

sendNumber: (value) => {

if (typeof value === 'number') {

return true // 验证通过

} else {

console.warn('传递的数据必须是数字类型')

return false // 验证失败

}

}

})

const sendNumberData = () => {

emit('sendNumber', '这是一个字符串') // 会触发警告

emit('sendNumber', 456) // 验证通过

}

</script>

二、兄弟组件通信

兄弟组件之间没有直接的通信渠道,通常需要借助父组件作为中间媒介,或者使用全局事件总线、状态管理工具(如 Pinia)。这里介绍借助父组件的方式。

2.1 实现原理

  • 组件 A(兄组件)通过子传父的方式将数据传递给父组件。
  • 父组件接收数据后,通过父传子的方式将数据传递给组件 B(弟组件)。
父组件示例
<template>

<div>

<BrotherA @sendToParent="handleBrotherASend" />

<BrotherB :dataFromBrotherA="dataFromA" />

</div>

</template>

<script setup>

import { ref } from 'vue'

import BrotherA from './components/BrotherA.vue'

import BrotherB from './components/BrotherB.vue'

const dataFromA = ref('')

// 接收 BrotherA 传递的数据

const handleBrotherASend = (data) => {

dataFromA.value = data

}

</script>
兄组件(BrotherA.vue)示例
<template>

<div>

<p>我是兄组件</p>

<button @click="sendToBrotherB">向弟组件发送数据</button>

</div>

</template>

<script setup>

import { defineEmits } from 'vue'

const emit = defineEmits(['sendToParent'])

const sendToBrotherB = () => {

emit('sendToParent', '来自兄组件的问候')

}

</script>
弟组件(BrotherB.vue)示例
<template>

<div>

<p>我是弟组件</p>

<p>从兄组件接收的数据:{{ dataFromBrotherA }}</p>

</div>

</template>

<script setup>

import { defineProps } from 'vue'

const props = defineProps({

dataFromBrotherA: String

})

</script>

三、跨级组件通信

当组件层级较深时,使用 props 和 emit 进行通信会比较繁琐,此时可以使用 provide 和 inject 实现跨级通信。

3.1 基本使用

  • 祖先组件使用 provide 提供数据或方法。
  • 后代组件使用 inject 注入并使用这些数据或方法。
祖先组件示例
<template>

<div>

<p>祖先组件:{{ message }}</p>

<button @click="changeMessage">修改消息</button>

<ParentComponent />

</div>

</template>

<script setup>

import { ref, provide } from 'vue'

import ParentComponent from './components/ParentComponent.vue'

const message = ref('这是祖先组件提供的数据')

// 提供数据,第一个参数是注入的 key,第二个参数是提供的值

provide('messageKey', message)

// 提供方法

provide('changeMessageKey', () => {

message.value = '祖先组件数据已修改'

})

const changeMessage = () => {

message.value = '祖先组件主动修改数据'

}

</script>
父组件(中间层,不处理数据)示例
<template>

<div>

<p>父组件</p>

<ChildComponent />

</div>

</template>

<script setup>

import ChildComponent from './ChildComponent.vue'

</script>
后代组件示例
<template>

<div>

<p>后代组件</p>

<p>从祖先组件注入的数据:{{ injectedMessage }}</p>

<button @click="injectedChangeMessage">调用祖先组件的方法</button>

</div>

</template>

<script setup>

import { inject } from 'vue'

// 注入数据,参数是祖先组件提供的 key

const injectedMessage = inject('messageKey')

// 注入方法

const injectedChangeMessage = inject('changeMessageKey')

</script>

provide 和 inject 可以跨越任意层级的组件,后代组件无论层级多深,都可以通过 inject 获取祖先组件提供的数据或方法。

3.2 处理默认值

当 inject 找不到对应的 key 时,可以设置默认值:

// 注入数据时设置默认值

const injectedData = inject('nonExistentKey', '默认值')

// 对于复杂类型的默认值,建议使用工厂函数,避免不必要的创建

const injectedObject = inject('objKey', () => ({ name: '默认名称' }))

四、不同通信方式的适用场景

通信方式

适用场景

优点

缺点

props / emit

父传子、子传父(直接父子关系)

简单直观,Vue 原生支持

层级较深时,需要逐层传递(props 透传问题)

父组件中转

兄弟组件通信

实现简单,无需额外工具

组件关系复杂时,逻辑分散,维护困难

provide / inject

跨级组件通信(层级较深)

无需关心组件层级,直接传递

数据追踪困难,不适合频繁变化的数据

状态管理工具(如 Pinia)

大型应用,多组件共享状态

集中管理状态,数据流向清晰

学习成本较高,小型应用可能过于复杂

全局事件总线

任意组件通信

灵活,可跨任意组件

事件命名容易冲突,缺乏类型检查,维护困难

在实际开发中,应根据组件关系和项目规模选择合适的通信方式。对于简单的父子组件通信,优先使用 props 和 emit;对于跨级通信,可考虑 provide 和 inject;对于大型应用的状态管理,建议使用 Pinia 等状态管理工具。

结语:

下一章我会讲解 Vue 3 中的状态管理工具 Pinia,包括它的基本概念、安装配置、核心用法以及在组件中的使用等内容,帮助宝子们更好地管理应用中的共享状态。如果你对前面的内容有疑问,或者想调整后续讲解的侧重点,请评论区留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冒气er

伸出你发财的小手叮咚一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值