在Vue應用開發中,組件化開發讓我們能夠構建可複用、易維護的代碼。但隨之而來的就是組件之間如何高效通信的問題。無論是父子組件、兄弟組件,還是跨級組件,都需要合適的通信方式。
1. Props / Emits - 最基礎的父子通信
Props 向下傳遞,Emits 向上傳遞,這是Vue組件通信的基石。
Props 父傳子
<!-- 父組件 -->
<template>
<ChildComponent :title="pageTitle" :user-info="userData" />
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const pageTitle = ref('用户管理')
const userData = ref({
name: '張三',
age: 25
})
</script>
<!-- 子組件 -->
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ userInfo.name }} - {{ userInfo.age }}歲</p>
</div>
</template>
<script setup>
defineProps({
title: String,
userInfo: Object
})
</script>
Emits 子傳父
<!-- 子組件 -->
<template>
<button @click="handleSubmit">提交</button>
</template>
<script setup>
const emit = defineEmits(['submit', 'update'])
const handleSubmit = () => {
emit('submit', { data: '提交的數據', time: new Date() })
}
</script>
<!-- 父組件 -->
<template>
<ChildComponent @submit="handleChildSubmit" />
</template>
<script setup>
const handleChildSubmit = (data) => {
console.log('接收到子組件數據:', data)
}
</script>
2. v-model - 雙向綁定的優雅實現
Vue3中的v-model得到了極大增強,讓雙向綁定更加靈活。
<!-- 自定義輸入組件 -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
class="custom-input"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<!-- 父組件使用 -->
<template>
<CustomInput v-model="username" />
<CustomInput v-model:title="pageTitle" /> <!-- 多個v-model -->
</template>
3. ref / $parent - 直接訪問組件實例
慎用但需要了解的通信方式,適合簡單場景。
<!-- 父組件 -->
<template>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">調用子組件方法</button>
</template>
<script setup>
import { ref } from 'vue'
const childRef = ref()
const callChildMethod = () => {
childRef.value.childMethod() // 直接調用子組件方法
}
</script>
<!-- 子組件 -->
<script setup>
const childMethod = () => {
console.log('子組件方法被調用')
}
// 暴露方法給父組件
defineExpose({
childMethod
})
</script>
4. Provide / Inject - 跨級組件通信的利器
解決多級組件傳遞props的繁瑣問題,適合深層組件通信。
<!-- 祖先組件 -->
<template>
<div>
<ParentComponent />
<button @click="updateTheme">切換主題</button>
</div>
</template>
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
const user = ref({ name: '李四', role: 'admin' })
// 提供數據
provide('theme', theme)
provide('user', user)
provide('updateTheme', () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
})
</script>
<!-- 深層子組件 -->
<template>
<div :class="`app-${theme}`">
<p>當前用户: {{ user.name }}</p>
<button @click="updateTheme">切換主題</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入數據
const theme = inject('theme')
const user = inject('user')
const updateTheme = inject('updateTheme')
</script>
5. Event Bus - 任意組件間通信
雖然Vue3推薦使用其他方式,但Event Bus在特定場景下仍然有用。
// eventBus.js
import { ref } from 'vue'
class EventBus {
constructor() {
this.events = ref({})
}
on(event, callback) {
if (!this.events.value[event]) {
this.events.value[event] = []
}
this.events.value[event].push(callback)
}
emit(event, data) {
if (this.events.value[event]) {
this.events.value[event].forEach(callback => callback(data))
}
}
off(event, callback) {
if (this.events.value[event]) {
this.events.value[event] = this.events.value[event].filter(cb => cb !== callback)
}
}
}
export const eventBus = new EventBus()
6. Pinia / Vuex - 狀態管理的終極方案
複雜應用的必備選擇,提供集中式狀態管理。
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
token: '',
permissions: []
}),
getters: {
isLoggedIn: (state) => !!state.token,
userPermissions: (state) => state.permissions
},
actions: {
async login(credentials) {
const response = await api.login(credentials)
this.user = response.user
this.token = response.token
this.permissions = response.permissions
},
logout() {
this.user = null
this.token = ''
this.permissions = []
}
}
})
7. 插槽通信 - 靈活的UI組合
內容分發中的通信,讓組件更加靈活。
<!-- 子組件 -->
<template>
<div class="card">
<header>
<slot name="header" :title="title" :toggle="toggleExpanded">
<h3>{{ title }}</h3>
</slot>
</header>
<main v-show="isExpanded">
<slot :data="contentData" />
</main>
<footer>
<slot name="footer" :expanded="isExpanded" />
</footer>
</div>
</template>
<script setup>
import { ref } from 'vue'
const title = ref('卡片標題')
const isExpanded = ref(true)
const contentData = ref({ message: '插槽內容數據' })
const toggleExpanded = () => {
isExpanded.value = !isExpanded.value
}
</script>
<!-- 父組件使用 -->
<template>
<CardComponent>
<template #header="{ title, toggle }">
<div class="card-header">
<h2>{{ title }}</h2>
<button @click="toggle">切換</button>
</div>
</template>
<template #default="{ data }">
<p>接收到的數據: {{ data.message }}</p>
</template>
<template #footer="{ expanded }">
<p>當前狀態: {{ expanded ? '展開' : '收起' }}</p>
</template>
</CardComponent>
</template>
8. 組合式函數 - 邏輯複用的新範式
抽取可複用邏輯,讓通信更加清晰。
其中,javascript代碼:
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const double = computed(() => count.value * 2)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = initialValue
}
return {
count,
double,
increment,
decrement,
reset
}
}
html部分如下:
<!-- 組件中使用 -->
<template>
<div>
<p>計數: {{ count }}</p>
<p>雙倍: {{ double }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, double, increment, decrement, reset } = useCounter(0)
</script>
如何選擇合適的通信方式?
|
場景
|
推薦方案
|
理由
|
|
父子組件簡單通信 |
Props/Emits |
直接、明確 |
|
表單雙向綁定 |
v-model |
語法糖、簡潔 |
|
深層組件傳值 |
Provide/Inject |
避免prop逐層傳遞 |
|
全局狀態管理 |
Pinia |
類型安全、DevTools支持 |
|
組件邏輯複用 |
組合式函數 |
邏輯抽離、可測試 |
|
臨時事件通知 |
Event Bus |
靈活、解耦 |
|
UI內容定製 |
插槽通信 |
靈活的內容分發 |
總結
在Vue3開發中,組件通信是構建複雜應用的核心技能。從最基礎的Props/Emits父子傳值,到現代化的Provide/Inject跨級通信,再到功能強大的Pinia狀態管理,每種方式都有其獨特的適用場景。簡單數據傳遞可選Props,深層組件通信適合Provide/Inject,複雜全局狀態管理推薦Pinia,而邏輯複用則可以考慮組合式函數。根據實際需求靈活選擇通信方案,既能保證代碼的清晰度,又能提升開發效率和可維護性,這才是Vue3組件通信的真正精髓所在。