Bootstrap

Vue 3 组件通信教程

Vue 3 组件通信教程

1. Props 父传子

1.1 基础用法

在 Vue 3 中,我们使用 defineProps 来声明组件的 props:

<!-- 子组件 ChildComponent.vue -->
<script setup>
const props = defineProps({
  message: String,
  count: {
    type: Number,
    required: true,
    default: 0
  },
  items: {
    type: Array,
    default: () => []
  }
})
</script>

<template>
  <div>
    <p>{{ message }}</p>
    <p>Count: {{ count }}</p>
  </div>
</template>

父组件中使用:

<!-- 父组件 ParentComponent.vue -->
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const parentMessage = ref('Hello from parent')
const parentCount = ref(42)
</script>

<template>
  <ChildComponent 
    :message="parentMessage"
    :count="parentCount"
  />
</template>

1.2 Props 验证

Props 可以设置详细的验证规则:

<script setup>
const 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)
    }
  }
})
</script>

2. Emits 子传父

2.1 基础用法

使用 defineEmits 声明事件:

<!-- 子组件 ChildComponent.vue -->
<script setup>
const emit = defineEmits(['update', 'delete'])

const handleClick = () => {
  emit('update', { id: 1, data: 'new value' })
}
</script>

<template>
  <button @click="handleClick">更新数据</button>
</template>

父组件中接收事件:

<!-- 父组件 ParentComponent.vue -->
<script setup>
import ChildComponent from './ChildComponent.vue'

const handleUpdate = (payload) => {
  console.log('收到更新:', payload)
}
</script>

<template>
  <ChildComponent @update="handleUpdate" />
</template>

2.2 带验证的 Emits

<script setup>
const emit = defineEmits({
  // 不带验证函数
  click: null,
  // 带验证函数
  submit: (payload) => {
    if (!payload.email) {
      return false
    }
    return true
  }
})
</script>

3. v-model 双向绑定

3.1 基础用法

<!-- 子组件 CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

父组件中使用:

<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

const text = ref('')
</script>

<template>
  <CustomInput v-model="text" />
  <p>输入的内容:{{ text }}</p>
</template>

3.2 多个 v-model 绑定

<!-- 子组件 UserForm.vue -->
<script setup>
defineProps(['firstName', 'lastName'])
const emit = defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    :value="firstName"
    @input="emit('update:firstName', $event.target.value)"
  />
  <input
    :value="lastName"
    @input="emit('update:lastName', $event.target.value)"
  />
</template>

父组件使用:

<script setup>
import { ref } from 'vue'
import UserForm from './UserForm.vue'

const firstName = ref('')
const lastName = ref('')
</script>

<template>
  <UserForm
    v-model:firstName="firstName"
    v-model:lastName="lastName"
  />
</template>

4. provide/inject 依赖注入

4.1 基础用法

<!-- 父组件提供数据 -->
<script setup>
import { provide, ref } from 'vue'

const theme = ref('dark')
provide('theme', theme)
</script>

<!-- 子组件注入数据 -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme', 'light') // 第二个参数是默认值
</script>

4.2 响应式数据注入

<!-- 父组件 -->
<script setup>
import { provide, ref, readonly } from 'vue'

const count = ref(0)
const incrementCount = () => {
  count.value++
}

// 提供只读值和修改方法
provide('count', readonly(count))
provide('increment', incrementCount)
</script>

<!-- 子组件 -->
<script setup>
import { inject } from 'vue'

const count = inject('count')
const increment = inject('increment')
</script>

<template>
  <button @click="increment">{{ count }}</button>
</template>

5. EventBus 事件总线

虽然 Vue 3 移除了内置的事件总线,但我们可以使用第三方库或自己实现一个简单的事件总线:

// eventBus.js
import mitt from 'mitt'
export default mitt()

使用示例:

<!-- 组件 A -->
<script setup>
import eventBus from './eventBus'

const sendMessage = () => {
  eventBus.emit('custom-event', { message: 'Hello!' })
}
</script>

<!-- 组件 B -->
<script setup>
import { onMounted, onUnmounted } from 'vue'
import eventBus from './eventBus'

const handleEvent = (data) => {
  console.log(data.message)
}

onMounted(() => {
  eventBus.on('custom-event', handleEvent)
})

onUnmounted(() => {
  eventBus.off('custom-event', handleEvent)
})
</script>

6. refs 直接访问

6.1 模板引用

<!-- 父组件 -->
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childRef = ref(null)

onMounted(() => {
  // 访问子组件的方法或属性
  childRef.value.someMethod()
})
</script>

<template>
  <ChildComponent ref="childRef" />
</template>

<!-- 子组件 -->
<script setup>
// 需要显式暴露方法给父组件
defineExpose({
  someMethod() {
    console.log('方法被调用')
  }
})
</script>

最佳实践建议

  1. 优先使用 props 和 emits 进行父子组件通信
  2. 对于跨多层组件的通信,考虑使用 provide/inject
  3. 对于全局状态管理,使用 Vuex 或 Pinia
  4. 避免过度使用 EventBus,它可能导致维护困难
  5. 谨慎使用 refs 直接访问子组件,这可能破坏组件封装性

注意事项

  1. Props 是只读的,不要在子组件中直接修改
  2. 使用 v-model 时注意命名冲突
  3. provide/inject 的响应式数据建议使用 readonly 包装
  4. 在组件卸载时记得清理事件监听器
  5. 使用 TypeScript 时,建议为 props 和 emits 添加类型声明
;