除了provide和inject,Vue3还有哪些用于组件通信的方式?
目录
一、父子组件通信(最基础场景)
1. props + emit(核心方案)
2. v-model(双向绑定语法糖)
3. ref + 组件实例(父访问子方法 / 数据)
4. $parent / $children(层级访问,不推荐)
二、兄弟组件 / 跨层级组件通信
1. 事件总线(Event Bus)
2. 状态管理库(Pinia / Vuex)
三、通过插槽(Slots)传递内容 / 数据
四、attrs 与 listeners(属性透传)
总结:不同场景的选择建议
在 Vue3 中,组件通信的方式根据组件关系(父子、兄弟、跨层级、全局)有所不同,除了provide/inject,还有以下常用方式:
一、父子组件通信(最基础场景)
1. props + emit(核心方案)
- 作用:
props实现父组件向子组件传递数据;emit实现子组件向父组件传递事件 / 数据。 - 用法示例:
<!-- 父组件 Parent.vue -->
<template><!-- 父传子:通过props传递message --><Child :message="parentMsg" @handleChange="onChildChange" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const parentMsg = ref('来自父组件的数据')
const onChildChange = (newVal) => {parentMsg.value = newVal // 接收子组件传递的数据
}
</script><!-- 子组件 Child.vue -->
<template><div><p>{{ message }}</p><button @click="sendToParent">向父组件传值</button></div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
// 定义接收的props
const props = defineProps({message: {type: String,default: ''}
})
// 定义触发的事件
const emit = defineEmits(['handleChange'])
const sendToParent = () => {emit('handleChange', '来自子组件的新数据') // 子传父:通过emit触发事件
}
</script>
- 适用场景:直接父子组件,数据传递简单、单向(父→子)或事件触发(子→父)。
2. v-model(双向绑定语法糖)
- 作用:简化 “父组件 props 传递 + 子组件 emit 回传” 的双向绑定逻辑,本质是
props+emit的语法糖。 - 原理:父组件用
v-model:xxx="value"时,等价于:- 向子组件传递
xxxprops - 监听子组件触发的
update:xxx事件
- 向子组件传递
- 用法示例:
<!-- 父组件 -->
<template><Child v-model:count="num" /> <!-- 双向绑定count -->
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const num = ref(0)
</script><!-- 子组件 -->
<template><button @click="add">count: {{ count }}</button>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({ count: Number })
const emit = defineEmits(['update:count']) // 固定事件名:update:xxx
const add = () => {emit('update:count', props.count + 1) // 触发更新,父组件num会同步变化
}
</script>
- 适用场景:需要父子组件双向同步数据(如表单组件、计数器等)。
3. ref + 组件实例(父访问子方法 / 数据)
- 作用:父组件通过
ref获取子组件实例,直接访问子组件的方法、数据或 DOM。 - 用法示例:
<!-- 父组件 -->
<template><Child ref="childRef" /><button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null) // 绑定子组件实例
const callChildMethod = () => {childRef.value?.childMethod() // 调用子组件的方法console.log(childRef.value.childData) // 访问子组件的数据
}
</script><!-- 子组件 -->
<script setup>
import { ref } from 'vue'
const childData = ref('子组件的数据')
const childMethod = () => {console.log('子组件方法被调用')
}
// 需通过defineExpose暴露(否则父组件无法访问)
defineExpose({ childData, childMethod })
</script>
- 注意:子组件需用
defineExpose显式暴露需要被访问的属性 / 方法(默认封闭)。 - 适用场景:父组件需要主动触发子组件的逻辑(如表单重置、弹窗显示等)。
4. $parent / $children(层级访问,不推荐)
- 作用:子组件通过
$parent访问父组件实例;父组件通过$children访问子组件列表。 - 缺点:强依赖组件层级,若组件嵌套关系变化(如增加中间层),会导致逻辑失效,维护性差。
- 用法(仅作了解,不推荐使用):
<!-- 子组件中访问父组件 -->
<script setup>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
console.log(instance.parent) // 父组件实例
</script>
二、兄弟组件 / 跨层级组件通信
1. 事件总线(Event Bus)
- 作用:通过一个全局事件中心,实现任意组件间的通信(发布 - 订阅模式)。
- 实现方式:Vue3 移除了内置的
$on/$off,需用第三方库(如mitt)或自定义事件中心。 - 用法示例(基于
mitt):- 安装:
npm install mitt - 创建事件总线:
// bus.js import mitt from 'mitt' export const bus = mitt() - 组件 A(发布事件):
<script setup> import { bus } from './bus.js' const sendMsg = () => {bus.emit('msgEvent', '来自组件A的消息') // 发布事件 } </script> - 组件 B(订阅事件):
<script setup> import { bus } from './bus.js' import { onMounted, onUnmounted } from 'vue' onMounted(() => {bus.on('msgEvent', (data) => { // 订阅事件console.log('收到消息:', data)}) }) onUnmounted(() => {bus.off('msgEvent') // 解绑事件,避免内存泄漏 }) </script>
- 安装:
- 适用场景:中小型项目,非全局的跨组件通信(如兄弟组件、隔代组件)。
2. 状态管理库(Pinia / Vuex)
- 作用:通过全局状态池管理数据,任意组件可共享、修改数据(解决 “全局状态共享” 问题)。
- Vue3 推荐用 Pinia(Vuex 的替代者,更简洁):
- 定义 store:
// stores/counter.js import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),actions: {increment() { this.count++ }} }) - 任意组件使用:
<script setup> import { useCounterStore } from './stores/counter.js' const store = useCounterStore() console.log(store.count) // 访问全局状态 store.increment() // 修改全局状态(所有组件会同步更新) </script>
- 定义 store:
- 适用场景:大型项目,需要全局共享状态(如用户信息、主题配置、购物车数据等)。
三、通过插槽(Slots)传递内容 / 数据
- 作用:父组件向子组件传递 “模板内容”,作用域插槽还可实现子组件向父组件传递数据。
- 分类:
- 默认插槽:传递基础内容;
- 具名插槽:传递多个不同区域的内容;
- 作用域插槽:子组件向父组件传递数据,父组件根据数据渲染内容。
- 作用域插槽示例(子传父数据):
<!-- 子组件 Child.vue --> <template><!-- 子组件通过v-slot传递数据(user) --><slot name="userInfo" :user="userData"></slot> </template> <script setup> import { ref } from 'vue' const userData = ref({ name: '张三', age: 20 }) // 子组件的数据 </script><!-- 父组件 Parent.vue --> <template><Child><!-- 父组件接收子组件传递的user数据并渲染 --><template #userInfo="{ user }"><p>姓名:{{ user.name }}</p><p>年龄:{{ user.age }}</p></template></Child> </template> - 适用场景:父组件需要控制子组件的部分 UI 渲染,且渲染内容依赖子组件的数据。
四、attrs 与 listeners(属性透传)
- 作用:当组件嵌套多层时,父组件的属性 / 事件可通过
attrs透传给深层子组件(避免每层手动传递)。 - Vue3 中:
$attrs包含所有未被props接收的属性(包括事件),可通过v-bind="$attrs"透传。 - 用法示例:
<!-- 父组件 --> <template><MiddleComponent msg="透传的消息" @notify="handleNotify" /> </template><!-- 中间组件 MiddleComponent.vue(无需处理,直接透传) --> <template><DeepComponent v-bind="$attrs" /> <!-- 将attrs透传给深层组件 --> </template><!-- 深层组件 DeepComponent.vue --> <template><p>{{ msg }}</p> <!-- 直接使用透传的msg --> </template> <script setup> import { defineProps, defineEmits } from 'vue' const props = defineProps({ msg: String }) const emit = defineEmits(['notify']) // 可触发透传的事件 </script> - 适用场景:组件库开发、多层嵌套组件的属性 / 事件透传(如 UI 组件的原生属性透传)。
总结:不同场景的选择建议
| 组件关系 | 推荐方式 | 特点 |
|---|---|---|
| 父子组件 | props + emit / v-model | 简单直接,单向 / 双向绑定 |
| 父子组件(交互) | ref + 组件实例 | 父主动调用子组件逻辑 |
| 兄弟 / 隔代组件 | 事件总线(mitt)/ provide/inject | 轻量跨层级通信 |
| 全局任意组件 | Pinia(状态管理) | 全局状态共享,适合大型项目 |
| 传递模板内容 | 插槽(尤其是作用域插槽) | 灵活控制子组件 UI 渲染 |
| 多层属性透传 | attrs 透传 | 简化嵌套组件的属性传递 |
根据项目规模和组件关系选择合适的方式,可大幅提升代码的可维护性。
