Vue3 组件通信与事件处理指南

一、Vue3 组件通信核心概念

1. 父组件 → 子组件 (Props)

  • 数据流向:父组件传递数据给子组件
  • 声明位置:在子组件中声明props
  • 触发位置:父组件中通过属性传递值
<!-- 父组件 Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const parentData = ref("来自父组件的数据")
</script>

<template>
  <Child :message="parentData" />
</template>

<!-- 子组件 Child.vue -->
<script setup>
defineProps({
  message: String // 声明接收的prop
})
</script>

<template>
  <div class="child">{{ message }}</div>
</template>

2. 子组件 → 父组件 (自定义事件)

  • 数据流向:子组件传递数据给父组件
  • 声明位置:在子组件中通过defineEmits声明事件
  • 触发位置:父组件中通过@事件名监听

vue

<!-- 子组件 Child.vue -->
<script setup>
// 声明自定义事件
const emit = defineEmits(['child-event'])

function sendData() {
  // 触发自定义事件
  emit('child-event', '来自子组件的数据')
}
</script>

<template>
  <button @click="sendData" class="btn">发送数据到父组件</button>
</template>

<!-- 父组件 Parent.vue -->
<script setup>
function handleData(data) {
  alert(`接收到子组件数据: ${data}`)
}
</script>

<template>
  <Child @child-event="handleData" />
</template>

3. 兄弟组件通信 (通过共同父组件)

  • 数据流向:组件A → 父组件 → 组件B
  • 实现方式:结合props和自定义事件
<!-- 父组件 Parent.vue -->
<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

const sharedData = ref(null)

function handleData(data) {
  sharedData.value = data
}
</script>

<template>
  <div class="sibling-container">
    <ComponentA @send-data="handleData" />
    <ComponentB :data="sharedData" />
  </div>
</template>

<!-- 组件A.vue -->
<script setup>
const emit = defineEmits(['send-data'])

function send() {
  emit('send-data', '来自组件A的数据')
}
</script>

<template>
  <div class="component-a">
    <button @click="send">发送数据给组件B</button>
  </div>
</template>

<!-- 组件B.vue -->
<script setup>
defineProps(['data'])
</script>

<template>
  <div class="component-b">
    <div v-if="data">接收到: {{ data }}</div>
    <div v-else>等待数据...</div>
  </div>
</template>

二、Vue3 特有通信方式

1. provide/inject (依赖注入)

  • 跨层级组件通信
  • 祖先组件提供数据,后代组件注入使用
<!-- 祖先组件 Ancestor.vue -->
<script setup>
import { provide, ref } from 'vue'
import Parent from './Parent.vue'

const theme = ref('dark')

// 提供数据给所有后代组件
provide('theme', theme)

function toggleTheme() {
  theme.value = theme.value === 'dark' ? 'light' : 'dark'
}
</script>

<template>
  <div :class="['ancestor', theme]">
    <button @click="toggleTheme">切换主题</button>
    <Parent />
  </div>
</template>

<!-- 后代组件 Descendant.vue -->
<script setup>
import { inject } from 'vue'

// 注入祖先组件提供的数据
const theme = inject('theme')
</script>

<template>
  <div :class="['descendant', theme]">
    当前主题: {{ theme }}
  </div>
</template>

2. 模板引用 (ref)

  • 直接访问子组件实例
<!-- 父组件 Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const childRef = ref(null)

function callChildMethod() {
  if (childRef.value) {
    childRef.value.childMethod()
  }
}
</script>

<template>
  <Child ref="childRef" />
  <button @click="callChildMethod">调用子组件方法</button>
</template>

<!-- 子组件 Child.vue -->
<script setup>
defineExpose({
  childMethod: () => {
    alert('子组件方法被调用!')
  }
})
</script>

<template>
  <div class="child">子组件</div>
</template>

3. 事件总线替代方案 (mitt)

  • 用于非父子组件通信
// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()

// 组件A (发送事件)
import { emitter } from './eventBus'
emitter.emit('global-event', '全局数据')

// 组件B (接收事件)
import { emitter } from './eventBus'
emitter.on('global-event', (data) => {
  console.log('收到全局事件:', data)
})

三、事件声明与触发规则

事件类型声明位置触发位置Vue3 语法
原生DOM事件子组件模板子组件内部<button @click="...">
自定义事件子组件中使用 defineEmits子组件内部emit('event-name', data)
Props传递事件父组件模板父组件中<Child @custom="handler"/>
全局事件任意组件任意组件mitt/emitter

四、最佳实践与常见问题

1. 保持单向数据流

// 子组件中不要直接修改props
defineProps(['initialValue'])

// 正确做法:触发事件让父组件修改
const emit = defineEmits(['update:value'])
emit('update:value', newValue)

// 父组件使用v-model
<Child v-model:value="parentValue" />

2. 组合式函数复用逻辑

// useCounter.js
import { ref } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  
  return { count, increment, decrement }
}

// 组件中使用
<script setup>
import { useCounter } from './useCounter'

const { count, increment } = useCounter()
</script>

3. 事件声明规范

  • 使用kebab-case命名事件:@update-value
  • 在子组件顶部使用defineEmits声明事件
  • 对于复杂数据类型,使用TypeScript定义事件载荷
<script setup lang="ts">
// 使用TypeScript定义事件
const emit = defineEmits<{
  (e: 'update:user', user: User): void
  (e: 'delete', id: number): void
}>()
</script>

4. 常见问题解答

事件应该在哪个组件声明?

  • 原生事件:在触发它的组件中声明
  • 自定义事件:在触发它的组件中声明(使用defineEmits),在需要响应它的组件中监听

多个组件需要共享数据怎么办?

  1. 使用provide/inject跨层级传递
  2. 使用Pinia/Vuex进行状态管理
  3. 使用组合式函数复用逻辑

子组件可以直接修改props吗?
不可以!应该:

  1. 使用v-model语法糖
  2. 通过自定义事件通知父组件修改

五、总结流程图

Props
defineEmits 事件
emit
Props
provide
mitt/emitter
ref
父组件
子组件
组件A
父组件
组件B
祖先组件
后代组件
组件A
组件B
父组件
子组件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值