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>
最佳实践建议
- 优先使用 props 和 emits 进行父子组件通信
- 对于跨多层组件的通信,考虑使用 provide/inject
- 对于全局状态管理,使用 Vuex 或 Pinia
- 避免过度使用 EventBus,它可能导致维护困难
- 谨慎使用 refs 直接访问子组件,这可能破坏组件封装性
注意事项
- Props 是只读的,不要在子组件中直接修改
- 使用 v-model 时注意命名冲突
- provide/inject 的响应式数据建议使用 readonly 包装
- 在组件卸载时记得清理事件监听器
- 使用 TypeScript 时,建议为 props 和 emits 添加类型声明