文章目录
在 Vue.js 开发中,组件通信的高效实现需要根据不同的场景选择合适的方式。以下是几种典型方案及其适用场景,结合 Vue 3 特性进行说明:
一、基础通信方案
- Props/Emits(父子通信)
<!-- Parent -->
<Child :title="data" @update="handleUpdate"/>
<!-- Child -->
<script setup>
const props = defineProps(['title'])
const emit = defineEmits(['update'])
</script>
- 最佳场景:直接父子组件通信
- 优化技巧:使用
v-model
语法糖(支持多个v-model) - 注意事项:避免深层次 props 透传(prop drilling)
- 模板引用(直接访问)
<template>
<ChildComponent ref="childRef" />
</template>
<script setup>
const childRef = ref(null)
// 通过 childRef.value.methodName() 调用子组件方法
</script>
- 适用场景:需要直接操作子组件时
- 注意事项:破坏组件封装性,慎用
二、跨层级通信方案
- Provide/Inject(依赖注入)
// 祖先组件
import { provide } from 'vue'
provide('contextKey', reactive({
data: 'value',
method: () => {...}
}))
// 后代组件
import { inject } from 'vue'
const context = inject('contextKey')
- 优势:支持响应式数据
- 最佳实践:配合 Composition API 使用
- 适用场景:深层嵌套组件通信
- Event Bus(小型项目)
// 使用 mitt(Vue 3 推荐)
import mitt from 'mitt'
const emitter = mitt()
// 发送事件
emitter.emit('event', data)
// 接收事件
emitter.on('event', callback)
- 适用场景:小型应用临时方案
- 注意事项:需手动管理事件监听,可能产生内存泄漏
三、状态管理方案
- Pinia(推荐状态管理)
// store/counter.js
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
}
}
})
// 组件中使用
import { useCounterStore } from '@/store/counter'
const counter = useCounterStore()
- 优势:TypeScript 友好、轻量、组合式 API 支持
- 最佳实践:全局状态、跨组件共享数据
- 性能优化:使用 storeToRefs 解构保持响应式
- 自定义 Hook(逻辑复用)
// useSharedLogic.js
export function useSharedLogic() {
const sharedState = ref('')
const updateState = (value) => { sharedState.value = value }
return { sharedState, updateState }
}
// 多个组件中复用
const { sharedState, updateState } = useSharedLogic()
- 优势:逻辑复用且保持响应式
- 适用场景:多个组件需要共享相同逻辑
四、高级通信模式
- Teleport + 全局状态
<!-- 跨层级操控 UI -->
<teleport to="#modal-target">
<Modal :data="globalStore.modalData"/>
</teleport>
- 适用场景:全局弹窗、通知等 UI 组件
- 自定义指令通信
// 特殊场景下的通信方式
app.directive('data-comm', {
mounted(el, binding) {
el._dataHandler = binding.value
el.addEventListener('custom-event', binding.value)
},
beforeUnmount(el) {
el.removeEventListener('custom-event', el._dataHandler)
}
})
五、性能优化建议
- 合理使用
shallowRef/shallowReactive
处理大型数据 - 对于频繁更新的数据,使用
computed
缓存计算结果 - 使用
v-once
处理静态内容 - 在大型列表中采用虚拟滚动(如 vue-virtual-scroller)
- 使用
watchEffect
替代多个独立 watch
六、通信策略选择指南
场景 | 推荐方案 | 注意事项 |
---|---|---|
直接父子 | Props/Emits | 避免超过 3 层传递 |
兄弟组件 | 共同父级/状态管理 | 优先考虑状态管理 |
跨层级组件 | Provide/Inject | 配合响应式数据使用 |
全局状态 | Pinia | 模块化组织 store |
临时通信 | Event Bus | 小型项目适用 |
UI 组件 | Teleport + 插槽 | 保持 DOM 结构清晰 |
七、Vue 3 新特性优化
- 多个 v-model
<UserForm
v-model:name="formData.name"
v-model:email="formData.email"
/>
- Composition API 组织逻辑
// 使用 setup 语法组织通信逻辑
const { data, methods } = useComponentCommunication()
- Reactivity Transform(实验性)
// 简化 ref 使用
let count = $ref(0)
function increment() {
count++
}
选择通信方案时需考虑:
- 组件关系层级
- 数据流动方向
- 状态共享范围
- 项目规模大小
- 团队协作约定
接下来是一个综合电商应用案例,演示多种 Vue 3 组件通信方式的协同使用。该案例包含以下功能模块:
案例场景:电商平台
- 全局状态管理(Pinia)
- 深层组件通信(Provide/Inject)
- 父子组件通信(Props/Emits)
- 事件总线(紧急通知)
- Teleport 弹窗
- 自定义 Hook 复用逻辑
一、项目结构
src/
├── App.vue
├── components/
│ ├── ProductList.vue
│ ├── ShoppingCart.vue
│ ├── UserPrefs/
│ │ ├── ThemeToggle.vue
│ │ └── CurrencySelector.vue
│ └── Notifications/
│ ├── GlobalAlert.vue
│ └── Toast.vue
├── stores/
│ └── mainStore.js
└── hooks/
└── useLocalStorage.js
二、核心代码实现
1. Pinia 全局状态管理 (stores/mainStore.js)
import { defineStore } from 'pinia'
export const useMainStore = defineStore('main', {
state: () => ({
products: [],
cart: [],
theme: 'light',
currency: 'USD'
}),
actions: {
addToCart(product) {
const existing = this.cart.find(p => p.id === product.id)
existing ? existing.quantity++ : this.cart.push({...product, quantity: 1})
},
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light'
}
},
getters: {
cartTotal: (state) => {
return state.cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
}
})
2. 深层主题配置(App.vue)
<script setup>
import { provide, reactive } from 'vue'
import { useMainStore } from './stores/mainStore'
const store = useMainStore()
const appConfig = reactive({
theme: computed(() => store.theme),
currency: computed(() => store.currency)
})
// 为深层嵌套组件提供配置
provide('appConfig', appConfig)
</script>
<template>
<div :class="`theme-${appConfig.theme}`">
<UserPrefs/>
<ProductList/>
<ShoppingCart/>
<Teleport to="#modals">
<GlobalAlert/>
</Teleport>
</div>
</template>
3. 深层嵌套组件(UserPrefs/ThemeToggle.vue)
<script setup>
import { inject } from 'vue'
import { useMainStore } from '../../stores/mainStore'
const store = useMainStore()
const appConfig = inject('appConfig')
// 使用自定义 Hook 持久化存储
const { saveToLocal } = useLocalStorage()
const toggleTheme = () => {
store.toggleTheme()
saveToLocal('theme', store.theme)
}
</script>
<template>
<button @click="toggleTheme">
切换主题:{{ appConfig.theme === 'light' ? '🌞' : '🌙' }}
</button>
</template>
4. 父子组件通信(ProductList.vue)
<script setup>
import { useMainStore } from '../stores/mainStore'
import ProductItem from './ProductItem.vue'
const store = useMainStore()
// 从父组件接收搜索参数
const props = defineProps({
searchQuery: String,
maxPrice: Number
})
const filteredProducts = computed(() => {
return store.products.filter(p =>
p.name.includes(props.searchQuery) &&
p.price <= props.maxPrice
)
})
</script>
<template>
<div class="product-grid">
<ProductItem
v-for="product in filteredProducts"
:key="product.id"
:product="product"
@add-to-cart="store.addToCart"
/>
</div>
</template>
5. 事件总线紧急通知(plugins/eventBus.js)
import mitt from 'mitt'
export const emitter = mitt()
// 在组件中使用
import { emitter } from '../plugins/eventBus'
// 发送紧急通知
emitter.emit('emergency', {
type: 'warning',
message: '库存不足!'
})
// 接收通知
emitter.on('emergency', (payload) => {
showAlert(payload)
})
6. 自定义 Hook(hooks/useLocalStorage.js)
import { ref, watchEffect } from 'vue'
export function useLocalStorage(key, initialValue) {
const stored = localStorage.getItem(key)
const data = ref(stored ? JSON.parse(stored) : initialValue)
watchEffect(() => {
localStorage.setItem(key, JSON.stringify(data.value))
})
return { data }
}
7. Teleport 全局弹窗(GlobalAlert.vue)
<script setup>
import { ref } from 'vue'
import { emitter } from '../plugins/eventBus'
const alerts = ref([])
emitter.on('emergency', (payload) => {
alerts.value.push(payload)
setTimeout(() => {
alerts.value.shift()
}, 5000)
})
</script>
<template>
<div class="alert-container">
<div
v-for="(alert, index) in alerts"
:key="index"
:class="`alert-${alert.type}`"
>
{{ alert.message }}
</div>
</div>
</template>
三、通信方式综合应用
- Pinia:管理全局共享状态(购物车、商品数据、用户偏好)
- Provide/Inject:深层传递主题和货币配置
- Props/Emits:父子组件间的商品数据传递
- Event Bus:处理紧急库存通知等全局事件
- Teleport:实现跨 DOM 层级的全局弹窗
- 自定义 Hook:封装 localStorage 操作逻辑
- 模板引用:在需要直接操作 DOM 元素时使用(示例中未展示)
四、性能优化实践
- 在商品列表中使用
v-memo
优化渲染
<ProductItem
v-for="product in filteredProducts"
v-memo="[product.id, product.inStock]"
:key="product.id"
:product="product"
/>
- 使用
shallowRef
处理大型商品数据
const largeProductList = shallowRef([]) // 不会深度追踪变化
- 列表虚拟滚动优化
<VirtualScroller
:items="filteredProducts"
item-height="60"
>
<template v-slot="{ item }">
<ProductItem :product="item"/>
</template>
</VirtualScroller>
五、通信策略选择流程图
这个案例展示了如何在真实项目中综合运用多种通信方式,每种方案都应用于最适合的场景。关键要点:
- 根据组件关系选择通信方式
- 全局状态优先使用Pinia管理
- 深层嵌套配置使用Provide/Inject
- 紧急/临时事件使用事件总线
- 复用逻辑封装为自定义Hook
- 保持单向数据流原则
- 根据场景进行性能优化
————————————————
最后我们放松一下眼睛