在 Vue 3 中,父子组件之间的通信方式非常丰富,可以根据组件的关系、数据流方向、复杂程度来选择。下面我帮你整理成一个 清晰体系 ,包括 优缺点、使用场景、示例。
🧩 一、Props + Event(最常用、单向数据流)
1️⃣ 父组件 → 子组件:props
- 父组件通过
props向子组件传递数据 - 数据是响应式的,子组件可读不可直接修改父组件的值(可通过
v-model或自定义事件来实现双向绑定)
js
<!-- Parent.vue -->
<template>
<Child :msg="message" @change="handleChange" />
</template>
<script>
import Child from './Child.vue'
import { ref } from 'vue'
export default {
components: { Child },
setup() {
const message = ref('Hello Vue 3')
const handleChange = (val) => { console.log('子组件通知:', val) }
return { message, handleChange }
}
}
</script>
js
<!-- Child.vue -->
<template>
<button @click="$emit('change', '子组件事件')">{{ msg }}</button>
</template>
<script>
export default {
props: ['msg']
}
</script>
2️⃣ 子组件 → 父组件:emit
- 子组件通过
$emit发事件通知父组件 - 常用于按钮点击、数据修改、事件触发等
🧩 二、v-model 双向绑定(语法糖)
- Vue 3 支持在子组件上自定义
v-model的 prop 名和事件名 - 常用于表单组件或数据需要双向绑定的场景
js
<!-- Parent.vue -->
<template>
<Child v-model:count="count" />
<p>{{ count }}</p>
</template>
<script>
import { ref } from 'vue'
import Child from './Child.vue'
export default { components: { Child }, setup() { const count = ref(0); return { count } } }
</script>
<!-- Child.vue -->
<template>
<button @click="$emit('update:count', count+1)">+1</button>
</template>
<script>
import { ref } from 'vue'
export default {
props: { count: Number },
setup(props) { return { count: props.count } }
}
</script>
🧩 三、Provide / Inject(跨级组件传递)
- 父组件使用
provide提供数据 - 子组件及深层组件使用
inject注入数据 - 用于多级组件通信、避免层层传递 props
js
<!-- GrandParent.vue -->
<script setup>
import { provide } from 'vue'
const theme = 'dark'
provide('theme', theme)
</script>
<!-- Child.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
console.log(theme) // 'dark'
</script>
⚠️ 注意:Provide / Inject 不是响应式的,如果需要响应式,提供的是
ref或reactive
🧩 四、通过 ref 获取子组件实例
- 父组件通过
ref获取子组件实例,调用子组件的方法 - 常用于非数据通信,比如调用子组件的内部方法、重置表单、播放视频等
js
<!-- Parent.vue -->
<template>
<Child ref="childRef" />
<button @click="callChild">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
const callChild = () => { childRef.value.sayHello() }
</script>
<!-- Child.vue -->
<script setup>
const sayHello = () => { console.log('Hello from child') }
</script>
🧩 五、全局状态管理(Vuex / Pinia / reactive store)
- 父子组件通过共享 全局 store 实现通信
- 适合跨组件、跨页面共享状态
- Vue 3 推荐 Pinia 作为轻量化状态管理
js
// store.js
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => ({ count: 0 }),
actions: { increment() { this.count++ } }
})
// Parent.vue / Child.vue
import { useStore } from './store'
const store = useStore()
store.increment() // 任意组件都可以修改
🧩 六、Event Bus(自定义事件总线)
- Vue 3 不再内置 EventBus,但可以用
mitt创建事件总线 - 常用于兄弟组件通信,或者父子组件通信也可
- 不推荐大量使用,容易造成维护困难
js
// bus.js
import mitt from 'mitt'
export const bus = mitt()
// Parent.vue
import { bus } from './bus.js'
bus.on('myEvent', val => console.log(val))
// Child.vue
import { bus } from './bus.js'
bus.emit('myEvent', 'hello bus')
🧠 总结
| 通信方式 | 方向 | 场景 | 备注 |
|---|---|---|---|
| Props + emit | 父 → 子 / 子 → 父 | 常规父子通信 | 最推荐 |
| v-model | 父 ↔ 子 | 表单 / 双向绑定 | Vue 3 支持自定义 prop 名 |
| provide / inject | 父 → 多级子 | 跨级传值 | 避免层层传递 props |
| ref 调用子组件方法 | 父 → 子 | 调用方法 | 非数据通信 |
| Pinia / Vuex | 全局 | 跨组件 / 跨页面 | 状态管理必备 |
| Event Bus (mitt) | 全局 / 任意方向 | 兄弟组件 / 临时通信 | 不推荐大量使用 |
总结一句话:
一般情况用 Props + emit / v-model;跨级用 provide/inject;跨组件用 Pinia;调用方法用 ref;EventBus 仅作补充。