标准答案

  1. 父传子用 props,子传父用 emits。这是最基础、最清晰的单向数据流。
  2. 父子双向输入组件可以用 v-model,本质仍然是 prop + update 事件。
  3. 兄弟组件优先把共同状态提升到共同父组件,再分别通过 props 和 emits 传递。
  4. 跨层级但范围明确的数据可以用 provide / inject,例如表单上下文、主题、局部配置、组件库内部状态。
  5. 多个页面或多个业务模块都要共享的状态,适合放进 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 传递,组件接口被无关字段污染。
  • 大量使用事件总线,导致事件来源、监听者和释放时机都不清楚。

作者信息