在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組件通信的真正精髓所在。