Vue2 组件通信方式
- Props(父传子)
<!-- 父组件 Parent.vue -->
<template>
<Child :title="parentTitle" :user="userData" />
</template>
<script>
import Child from './Child.vue'
export default {
components: { Child },
data() {
return {
parentTitle: 'Hello from Parent',
userData: { name: 'John', age: 25 }
}
}
}
</script>
<!-- 子组件 Child.vue -->
<template>
<div>
<h2>{{ title }}</h2>
<p>Name: {{ user.name }}</p >
</div>
</template>
<script>
export default {
props: {
// 基础类型
title: {
type: String,
required: true,
default: 'Default Title'
},
// 对象类型
user: {
type: Object,
default: () => ({})
}
}
}
</script>
- $emit(子传父)
<!-- 子组件 Child.vue -->
<template>
<button @click="sendMessage">发送消息给父组件</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('message-from-child', 'Hello Parent!')
}
}
}
</script>
<!-- 父组件 Parent.vue -->
<template>
<Child @message-from-child="handleChildMessage" />
</template>
<script>
export default {
methods: {
handleChildMessage(msg) {
console.log('收到子组件消息:', msg)
}
}
}
</script>
- Event Bus(全局事件总线)
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 组件A - 发送事件
<script>
import { EventBus } from './eventBus'
export default {
methods: {
sendData() {
EventBus.$emit('global-event', { data: 'some data' })
}
}
}
</script>
// 组件B - 接收事件
<script>
import { EventBus } from './eventBus'
export default {
created() {
EventBus.$on('global-event', (payload) => {
console.log('收到事件:', payload)
})
},
beforeDestroy() {
EventBus.$off('global-event')
}
}
</script>
- Vuex(状态管理)
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state, payload) {
state.count += payload.amount
}
},
actions: {
incrementAsync({ commit }, payload) {
setTimeout(() => {
commit('increment', payload)
}, 1000)
}
}
})
// 组件中使用
<script>
export default {
computed: {
count() {
return this.$store.state.count
}
},
methods: {
increment() {
this.$store.commit('increment', { amount: 1 })
// 或使用actions
this.$store.dispatch('incrementAsync', { amount: 1 })
}
}
}
</script>
$attrs和$listeners
<!-- 父组件 Parent.vue -->
<template>
<Child
title="传递的标题"
:data="someData"
@custom-event="handleEvent"
/>
</template>
<!-- 中间组件 Child.vue -->
<template>
<!-- 传递所有属性和事件 -->
<GrandChild v-bind="$attrs" v-on="$listeners" />
</template>
<script>
export default {
inheritAttrs: false // 不将attrs设置为DOM属性
}
</script>
<!-- 最终组件 GrandChild.vue -->
<template>
<div>
<h2>{{ title }}</h2>
<button @click="$emit('custom-event')">触发事件</button>
</div>
</template>
<script>
export default {
props: ['title']
}
</script>
Vue3 组件通信方式
- Props(父传子) - Composition API
<!-- 父组件 Parent.vue -->
<template>
<Child
:title="parentTitle"
:user="userData"
@update:title="parentTitle = $event"
/>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const parentTitle = ref('Hello from Parent')
const userData = ref({ name: 'John', age: 25 })
</script>
<!-- 子组件 Child.vue -->
<template>
<div>
<h2>{{ title }}</h2>
<button @click="updateTitle">修改标题</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
title: {
type: String,
required: true,
default: 'Default Title'
},
user: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['update:title'])
const updateTitle = () => {
emit('update:title', 'New Title from Child')
}
</script>
- defineEmits(子传父)
<!-- 子组件 Child.vue -->
<template>
<button @click="sendData">发送数据</button>
</template>
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits({
// 带验证的emit
'send-message': (payload) => {
return typeof payload === 'string'
}
})
const sendData = () => {
emit('send-message', 'Hello from Child')
emit('update:count', 10) // 支持v-model语法
}
</script>
<!-- 父组件 Parent.vue -->
<template>
<Child
@send-message="handleMessage"
@update:count="count = $event"
/>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const handleMessage = (msg) => {
console.log('收到:', msg)
}
</script>
- provide/inject(跨层级通信)
<!-- 祖先组件 Ancestor.vue -->
<template>
<Parent />
</template>
<script setup>
import { provide, ref } from 'vue'
import Parent from './Parent.vue'
const sharedData = ref('共享数据')
const updateSharedData = (newValue) => {
sharedData.value = newValue
}
// 提供数据和方法
provide('sharedData', {
sharedData,
updateSharedData
})
</script>
<!-- 后代组件 Descendant.vue -->
<template>
<div>{{ data.sharedData }}</div>
</template>
<script setup>
import { inject } from 'vue'
const data = inject('sharedData')
const updateData = () => {
data.updateSharedData('新的共享数据')
}
</script>
- Pinia(Vue3推荐的状态管理)
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment(amount = 1) {
this.count += amount
}
},
getters: {
doubleCount: (state) => state.count * 2
}
})
// 组件中使用
<template>
<div>{{ store.count }}</div>
<div>双倍: {{ store.doubleCount }}</div>
<button @click="store.increment()">增加</button>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
</script>
- 模板引用和 defineExpose
<!-- 父组件 Parent.vue -->
<template>
<Child ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
const callChildMethod = () => {
childRef.value?.childMethod()
console.log('子组件数据:', childRef.value?.childData)
}
</script>
<!-- 子组件 Child.vue -->
<template>
<div>子组件</div>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const childData = ref('子组件数据')
const childMethod = () => {
console.log('子组件方法被调用')
}
// 暴露给父组件
defineExpose({
childData,
childMethod
})
</script>
- 自定义Hooks(组合式函数)
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const double = computed(() => count.value * 2)
return {
count,
increment,
decrement,
double
}
}
// 组件中使用
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, increment, double } = useCounter(10)
</script>
Vue2 vs Vue3 通信方式对比
| 特性 | Vue2 | Vue3 |
|---|---|---|
| Props声明 | props: {} 选项 | defineProps() 组合式API |
| 事件发射 | this.$emit() | defineEmits() |
| 跨组件通信 | provide/inject 选项 | provide()/inject() 函数 |
| 状态管理 | Vuex | Pinia(推荐)或 Vuex 4 |
| 暴露组件方法 | 自动暴露public方法 | defineExpose() 显式暴露 |
| 属性透传 | listeners | useAttrs() 和 useListeners() |
| 响应式数据 | data() 返回对象 | ref() 或 reactive() |
| 全局事件总线 | 需要创建Vue实例 | 使用Mitt等第三方库 |
最佳实践建议
Vue2项目:
- 简单父子通信使用 props/$emit
- 复杂状态管理使用 Vuex
- 跨层级组件使用 provide/inject
- 事件总线适合简单场景,注意及时清理
Vue3项目:
- 优先使用组合式API
- 状态管理推荐 Pinia
- 使用TypeScript增强类型安全
- 复杂逻辑抽离为组合式函数
- 注意响应式数据的正确使用