组件通信
组件通信的方式
在 Vue3 中,组件通信有多种方式,每种方式适用于不同的场景。
1. Props 向下传递
Props 是父组件向子组件传递数据的主要方式。
父组件
<template>
<ChildComponent
:message="parentMessage"
:user="userInfo"
:is-active="true"
/>
</template>
<script>
export default {
data() {
return {
parentMessage: 'Hello from parent',
userInfo: {
name: 'John',
age: 30
}
}
}
}
</script>
子组件
<template>
<div>
<p>{{ message }}</p>
<p>{{ user.name }} - {{ user.age }}</p>
<p v-if="isActive">Active</p>
</div>
</template>
<script>
export default {
props: {
// 基本类型
message: String,
// 对象类型
user: {
type: Object,
required: true
},
// 带默认值的布尔类型
isActive: {
type: Boolean,
default: false
}
}
}
</script>
2. Emits 向上传递事件
子组件通过 emits 向父组件发送事件,父组件可以监听这些事件并执行相应的处理函数。
子组件
<template>
<button @click="handleClick">点击我</button>
<input
type="text"
:value="inputValue"
@input="handleInput"
placeholder="输入内容"
/>
</template>
<script>
export default {
name: 'ChildComponent',
emits: ['click', 'input-change'],
data() {
return {
inputValue: ''
}
},
methods: {
handleClick() {
// 发送事件,不带参数
this.$emit('click')
},
handleInput(event) {
this.inputValue = event.target.value
// 发送事件,带参数
this.$emit('input-change', this.inputValue)
}
}
}
</script>
父组件
<template>
<ChildComponent
@click="handleChildClick"
@input-change="handleInputChange"
/>
<p>收到的输入: {{ receivedInput }}</p>
</template>
<script>
export default {
data() {
return {
receivedInput: ''
}
},
methods: {
handleChildClick() {
console.log('子组件被点击了')
},
handleInputChange(value) {
this.receivedInput = value
}
}
}
</script>
3. Provide / Inject 依赖注入
适用于跨多级组件通信,父组件提供数据,子组件或孙组件注入使用。
父组件(提供数据)
<template>
<div>
<ChildComponent />
</div>
</template>
<script>
export default {
provide() {
return {
appName: 'Vue3 App',
user: {
name: 'John',
age: 30
},
updateUser: this.updateUser
}
},
data() {
return {
user: {
name: 'John',
age: 30
}
}
},
methods: {
updateUser(newUser) {
this.user = newUser
}
}
}
</script>
子组件或孙组件(注入数据)
<template>
<div>
<p>应用名称: {{ appName }}</p>
<p>用户: {{ user.name }} - {{ user.age }}</p>
<button @click="handleUpdate">更新用户</button>
</div>
</template>
<script>
export default {
inject: ['appName', 'user', 'updateUser'],
methods: {
handleUpdate() {
this.updateUser({
name: 'Jane',
age: 25
})
}
}
}
</script>
4. 组合式 API 中的响应式共享
在组合式 API 中,可以使用响应式 API 结合 provide/inject 实现组件通信。
父组件
<template>
<div>
<ChildComponent />
</div>
</template>
<script setup>
import { ref, provide } from 'vue'
import ChildComponent from './ChildComponent.vue'
const count = ref(0)
function increment() {
count.value++
}
// 提供响应式数据和方法
provide('count', count)
provide('increment', increment)
</script>
子组件
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入响应式数据和方法
const count = inject('count')
const increment = inject('increment')
</script>
5. Event Bus 事件总线
适用于任意组件间通信,但在 Vue3 中推荐使用 mitt 库实现。
1. 安装 mitt
npm install mitt
2. 创建事件总线
// eventBus.js
import mitt from 'mitt'
export const eventBus = mitt()
3. 发送事件
// ComponentA.vue
import { eventBus } from './eventBus'
eventBus.emit('custom-event', { message: 'Hello from Component A' })
4. 监听事件
// ComponentB.vue
import { eventBus } from './eventBus'
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
// 监听事件
eventBus.on('custom-event', (data) => {
console.log('收到事件:', data)
})
})
onUnmounted(() => {
// 移除事件监听
eventBus.off('custom-event')
})
6. Vuex / Pinia 状态管理
对于复杂应用,推荐使用 Vuex 或 Pinia 进行集中状态管理。
组件通信方式对比
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Props | 父到子 | 简单直接,单向数据流清晰 | 多级传递繁琐 |
| Emits | 子到父 | 事件驱动,灵活 | 多级传递繁琐 |
| Provide/Inject | 跨多级组件 | 简化多级组件通信 | 依赖关系不明确 |
| Event Bus | 任意组件 | 灵活,解耦 | 事件管理复杂,易内存泄漏 |
| 状态管理 | 复杂应用 | 集中管理,清晰可预测 | 增加复杂度,学习成本高 |
最佳实践
- 优先使用 Props 和 Emits 进行父子组件通信
- 跨多级组件通信使用 Provide/Inject
- 复杂应用使用状态管理库
- Event Bus 谨慎使用,注意及时清理事件监听
- 保持组件通信的单向数据流,避免双向绑定导致的复杂度