Vue 组件通信方式
Props、Emit、EventBus、Vuex/Pinia、Provide/Inject 等组件通信方案
问题
Vue 中组件之间如何通信?常见的通信方式有哪些?
解答
1. Props / Emit(父子通信)
最基础的通信方式,父传子用 props,子传父用 emit。
<!-- Parent.vue -->
<template>
<Child :message="msg" @update="handleUpdate" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const msg = ref('Hello')
const handleUpdate = (newValue) => {
msg.value = newValue
}
</script>
<!-- Child.vue -->
<template>
<div>
<p>{{ message }}</p>
<button @click="emit('update', 'New Value')">更新</button>
</div>
</template>
<script setup>
// 定义 props
defineProps({
message: String
})
// 定义 emit
const emit = defineEmits(['update'])
</script>
2. Provide / Inject(跨层级通信)
祖先组件提供数据,后代组件注入使用,无需逐层传递。
<!-- Ancestor.vue -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
// 提供响应式数据
provide('theme', theme)
// 提供修改方法
provide('updateTheme', (newTheme) => {
theme.value = newTheme
})
</script>
<!-- Descendant.vue -->
<script setup>
import { inject } from 'vue'
// 注入数据,第二个参数是默认值
const theme = inject('theme', 'light')
const updateTheme = inject('updateTheme')
// 使用
updateTheme('light')
</script>
3. EventBus(任意组件通信)
Vue 3 移除了 $on,需要使用第三方库如 mitt。
// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()
<!-- ComponentA.vue -->
<script setup>
import { emitter } from './eventBus'
// 发送事件
const sendMessage = () => {
emitter.emit('custom-event', { data: 'Hello' })
}
</script>
<!-- ComponentB.vue -->
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { emitter } from './eventBus'
const handler = (payload) => {
console.log(payload.data)
}
onMounted(() => {
// 监听事件
emitter.on('custom-event', handler)
})
onUnmounted(() => {
// 移除监听,防止内存泄漏
emitter.off('custom-event', handler)
})
</script>
4. Pinia(全局状态管理)
Vue 3 推荐的状态管理方案,替代 Vuex。
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
<!-- 任意组件中使用 -->
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 读取状态
console.log(counter.count)
console.log(counter.doubleCount)
// 修改状态
counter.increment()
counter.count++
counter.$patch({ count: 10 })
</script>
5. $attrs(透传属性)
未被 props 声明的属性会自动透传,适合封装组件。
<!-- MyInput.vue -->
<template>
<!-- $attrs 自动包含未声明的属性 -->
<input v-bind="$attrs" :value="modelValue" @input="onInput" />
</template>
<script setup>
// 只声明需要处理的 props
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const onInput = (e) => {
emit('update:modelValue', e.target.value)
}
</script>
<script>
export default {
// 禁止自动继承到根元素
inheritAttrs: false
}
</script>
<!-- 使用时 -->
<template>
<!-- placeholder、disabled 等会透传到 input -->
<MyInput v-model="text" placeholder="请输入" disabled />
</template>
6. $parent / $refs(直接访问实例)
直接访问父组件或子组件实例,不推荐常用。
<!-- Parent.vue -->
<template>
<Child ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const childRef = ref(null)
onMounted(() => {
// 访问子组件暴露的方法
childRef.value.sayHello()
})
</script>
<!-- Child.vue -->
<script setup>
const sayHello = () => {
console.log('Hello from child')
}
// 必须显式暴露,否则父组件无法访问
defineExpose({
sayHello
})
</script>
通信方式选择
| 场景 | 推荐方式 |
|---|---|
| 父子组件 | Props / Emit |
| 跨多层组件 | Provide / Inject |
| 兄弟组件 | EventBus 或状态管理 |
| 全局状态 | Pinia |
| 组件封装透传 | $attrs |
| 直接调用子组件方法 | $refs + defineExpose |
关键点
- Props 向下传递,Emit 向上传递,是最基础的父子通信方式
- Provide/Inject 解决跨层级传递问题,但要注意响应式数据需要用 ref/reactive
- Vue 3 中 EventBus 需要用 mitt 等第三方库,记得在 onUnmounted 中移除监听
- Pinia 是 Vue 3 官方推荐的状态管理,比 Vuex 更简洁
$attrs配合inheritAttrs: false可以精确控制属性透传- 使用
$refs访问子组件时,子组件必须用defineExpose暴露方法
目录