组件通信概述
在 Vue 应用中,组件是独立的代码单元,它们需要相互通信以构建完整的应用功能。Vue 提供了多种组件通信方式,每种方式都有其适用场景:
- Props Down:父组件向子组件传递数据
- Events Up:子组件向父组件发送消息
- Provide/Inject:跨层级组件通信
- Event Bus:任意组件间的通信
- Vuex/Pinia:全局状态管理
Props:父组件向子组件传递数据
基础用法
<template> <div class="parent"> <child-component :message="parentMessage" :items="list" required-prop="必传值" /> </div></template>
<script setup>import { ref } from 'vue'import ChildComponent from './ChildComponent.vue'
const parentMessage = ref('来自父组件的消息')const list = ref(['苹果', '香蕉', '橙子'])</script>
<template> <div class="child"> <p>{{ message }}</p> <ul> <li v-for="item in items" :key="item">{{ item }}</li> </ul> <p>{{ requiredProp }}</p> </div></template>
<script setup>// 使用 defineProps 声明 propsconst props = defineProps({ message: String, items: Array, requiredProp: { type: String, required: true }})</script>
Props 验证
Vue 提供了丰富的 props 验证选项:
defineProps({ // 基础类型检查 propA: Number,
// 多种类型 propB: [String, Number],
// 必传 propC: { type: String, required: true },
// 带默认值 propD: { type: Number, default: 100 },
// 对象默认值 propE: { type: Object, default: () => ({ message: 'hello' }) },
// 自定义验证 propF: { validator(value) { return ['success', 'warning', 'danger'].includes(value) } }})
Emit:子组件向父组件发送消息
基础用法
<template> <div> <button @click="handleClick">点击发送消息</button> </div></template>
<script setup>const emit = defineEmits(['update', 'delete'])
function handleClick() { emit('update', { id: 1, data: '更新的数据' })}</script>
<template> <div> <child-component @update="handleUpdate" @delete="handleDelete" /> </div></template>
<script setup>function handleUpdate(payload) { console.log('收到更新事件:', payload)}
function handleDelete(id) { console.log('收到删除事件:', id)}</script>
v-model 的使用
v-model 是 props 和 emit 的语法糖,用于实现双向绑定:
<!-- 父组件 --><template> <custom-input v-model="searchText" /></template>
<!-- 子组件 --><template> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" ></template>
<script setup>defineProps(['modelValue'])defineEmits(['update:modelValue'])</script>
Provide/Inject:跨层级组件通信
当需要从父组件向深层子组件传递数据时,可以使用 provide/inject:
<script setup>import { provide, ref } from 'vue'
const theme = ref('dark')const updateTheme = (newTheme) => { theme.value = newTheme}
// 提供数据和方法provide('theme', { theme, updateTheme})</script>
<template> <div :class="theme.theme"> <button @click="toggleTheme"> 切换主题 </button> </div></template>
<script setup>import { inject } from 'vue'
// 注入数据和方法const theme = inject('theme')
function toggleTheme() { theme.updateTheme( theme.theme === 'dark' ? 'light' : 'dark' )}</script>
使用 Symbol 作为 Key
为了避免命名冲突,可以使用 Symbol 作为 provide/inject 的 key:
export const themeSymbol = Symbol('theme')
// 父组件provide(themeSymbol, theme)
// 子组件const theme = inject(themeSymbol)
事件总线:任意组件间通信
虽然 Vue 3 移除了内置的事件总线,但我们可以使用第三方库或自己实现一个简单的事件总线:
import { ref } from 'vue'
class EventBus { constructor() { this.events = ref(new Map()) }
on(event, callback) { if (!this.events.value.has(event)) { this.events.value.set(event, []) } this.events.value.get(event).push(callback) }
emit(event, data) { if (this.events.value.has(event)) { this.events.value.get(event).forEach(callback => callback(data)) } }
off(event, callback) { if (this.events.value.has(event)) { const callbacks = this.events.value.get(event) const index = callbacks.indexOf(callback) if (index > -1) callbacks.splice(index, 1) } }}
export default new EventBus()
使用事件总线:
// 组件 Aimport eventBus from './eventBus'
eventBus.emit('userLoggedIn', { id: 1, name: 'Alice' })
// 组件 Bimport eventBus from './eventBus'
eventBus.on('userLoggedIn', (user) => { console.log('用户登录:', user)})
最佳实践
-
选择合适的通信方式
- 父子组件:优先使用 props/emit
- 跨多层级:考虑 provide/inject
- 全局状态:使用 Vuex/Pinia
- 简单的全局通信:事件总线
-
Props 的设计原则
- 明确的命名
- 适当的类型验证
- 合理的默认值
- 必要的文档注释
-
避免过度通信
- 组件职责要单一
- 避免过深的组件层级
- 合理使用状态管理
总结
Vue 提供了丰富的组件通信方式,每种方式都有其适用场景:
- Props/Emit:最基础、最常用的父子组件通信方式
- Provide/Inject:适用于深层组件通信
- 事件总线:适用于简单的全局通信
- Vuex/Pinia:适用于复杂的状态管理
选择合适的通信方式对于构建可维护的 Vue 应用至关重要。在实际开发中,应根据具体场景选择最合适的通信方式,并遵循最佳实践原则。