标准答案
- 父传子用 props,子传父用 emits。这是最基础、最清晰的单向数据流。
- 父子双向输入组件可以用 v-model,本质仍然是 prop + update 事件。
- 兄弟组件优先把共同状态提升到共同父组件,再分别通过 props 和 emits 传递。
- 跨层级但范围明确的数据可以用 provide / inject,例如表单上下文、主题、局部配置、组件库内部状态。
- 多个页面或多个业务模块都要共享的状态,适合放进 Pinia 或 Vuex。临时事件通知可以用事件总线或 mitt,但不要把它当成主要状态管理方案。
题目解析
选择通信方式时先看状态范围。状态只属于一个父子组件,就不要上全局 store;状态跨很多页面,就不要一路 props drilling。
provide / inject 适合“局部全局”,比如 Form 给 FormItem 提供校验上下文。它不适合承载复杂业务全局状态。
事件总线在 Vue 2 老项目里常见,但事件来源和监听关系不直观。Vue 3 项目更推荐明确的 emits、组合式函数或状态库。
代码示例
这个例子展示父组件把状态交给子组件展示,子组件通过事件通知父组件修改。
Vue
<!-- Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import CounterButton from './CounterButton.vue'
const count = ref(0)
</script>
<template>
<CounterButton
:count="count"
@increment="count += 1"
/>
</template>Vue
<!-- CounterButton.vue -->
<script setup lang="ts">
defineProps<{
count: number
}>()
const emit = defineEmits<{
increment: []
}>()
</script>
<template>
<button type="button" @click="emit('increment')">
{{ count }}
</button>
</template>常见误区
- 所有状态都放 Pinia,导致局部组件状态全局化。
- 跨很多层级仍然强行逐层 props 传递,组件接口被无关字段污染。
- 大量使用事件总线,导致事件来源、监听者和释放时机都不清楚。