Vue组件通信

170 阅读2分钟

Vue 组件通信是构建复杂应用的基础,以下是 Vue 2 和 Vue 3 中所有主要的组件通信方式及其适用场景:

一、父子组件通信

1. Props(父 → 子)

<!-- 父组件 -->
<ChildComponent :title="pageTitle" :user="userData" />

<!-- 子组件 -->
<script>
export default {
  props: {
    title: String,
    user: {
      type: Object,
      default: () => ({})
    }
  }
}
</script>

特点:单向数据流,适合父向子传值

2. $emit / 自定义事件(子 → 父)

vue

复制

<!-- 子组件 -->
<button @click="$emit('update', newValue)">提交</button>

<!-- 父组件 -->
<ChildComponent @update="handleUpdate" />

特点:子组件触发父组件方法

3. v-model(双向绑定)

<!-- Vue 3 默认方式 -->
<CustomInput v-model="searchText" />

<!-- 子组件实现 -->
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

二、跨层级组件通信

4. Provide / Inject(依赖注入)

// 祖先组件 (Vue 3)
import { provide } from 'vue'
export default {
  setup() {
    provide('theme', 'dark')
  }
}

// 后代组件
import { inject } from 'vue'
export default {
  setup() {
    const theme = inject('theme', 'light') // 默认值
    return { theme }
  }
}

特点:适合深层嵌套组件通信

5. 事件总线(Event Bus)

// Vue 3 需要外部库(如 mitt)
// bus.js
import mitt from 'mitt'
export default mitt()

// 组件A(发送)
import bus from './bus'
bus.emit('event-name', data)

// 组件B(接收)
bus.on('event-name', data => { ... })

// 记得在组件卸载时移除监听
onUnmounted(() => {
  bus.off('event-name')
})

特点:任意组件间通信,但需注意内存泄漏

三、状态管理方案

6. Vuex(Vue 2 官方状态管理)

// store.js
import { createStore } from 'vuex'

export default createStore({
  state: { count: 0 },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => commit('increment'), 1000)
    }
  }
})

// 组件中使用
import { useStore } from 'vuex'
export default {
  setup() {
    const store = useStore()
    return {
      count: computed(() => store.state.count),
      increment: () => store.commit('increment')
    }
  }
}

7. Pinia(Vue 3 推荐状态管理)

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    }
  }
})

// 组件中使用
import { useCounterStore } from '@/stores/counter'
export default {
  setup() {
    const counter = useCounterStore()
    return { counter }
  }
}

特点:更简单、类型安全、组合式API友好

四、模板引用通信

8. ref / $refs

<!-- 父组件 -->
<ChildComponent ref="child" />

<script>
export default {
  mounted() {
    this.$refs.child.methodName() // 调用子组件方法
    this.$refs.child.property = value // 访问子组件属性
  }
}
</script>

特点:直接访问组件实例,但应谨慎使用

五、其他通信方式

9. attrs/attrs/listeners(Vue 2)

<!-- 父组件 -->
<ChildComponent v-bind="$attrs" v-on="$listeners" />

<!-- Vue 3 中已合并到 $attrs -->

10. parent/parent/children

// 子组件中
this.$parent.methodName()

// 父组件中
this.$children[0].property = value

特点:不推荐使用,破坏组件封装性

11. 全局状态(不推荐)

// main.js
app.config.globalProperties.$appName = 'My App'

// 组件中
this.$appName

六、通信方式选择指南

通信方式适用场景Vue 2Vue 3备注
Props父→子数据传递推荐首选
$emit子→父事件通知推荐首选
v-model表单双向绑定语法糖
Provide/Inject跨层级传递适合主题/配置
事件总线任意组件通信⚠️需库注意内存泄漏
Vuex复杂状态管理⚠️兼容Vue 2首选
Pinia现代状态管理Vue 3首选
$refs直接访问组件应急方案
$attrs属性透传高阶组件
$parent访问父实例不推荐

最佳实践建议

  1. 简单场景:优先使用 props + emit
  2. 兄弟组件:提升状态到共同父级或使用状态管理
  3. 跨层级:使用 Provide/Inject 或状态管理
  4. 全局状态:使用 Pinia/Vuex
  5. 避免:直接修改子组件状态($refs)、过度使用事件总线
  6. TypeScript:为所有通信方式添加类型定义

Vue 3 组合式API通信示例

// useCounter.js - 可组合函数
import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++
  
  return { count, increment }
}

// 组件A
import { useCounter } from './useCounter'
const { count, increment } = useCounter()

// 组件B
import { useCounter } from './useCounter'
const { count } = useCounter() // 共享状态

选择通信方式时应考虑组件关系、数据流清晰度和长期维护成本。对于新项目,Vue 3 + Pinia + 组合式API是最佳选择。