Logo

Vue3 组件通信实战,实现跨组件数据更新

author
YGHub·2024-11-21·8·字数:443 字·阅读时间:2 分钟

在实际开发中,经常需要一个组件的操作触发另一个组件的更新。

比如:点击头部的刷新按钮,让列表组件重新加载数据…

一、Props + Emit 方式(父子组件)

最基本的父子组件通信方式:

vue
// ParentComponent.vue
 
<template>
<div>
<!-- 传递更新函数给子组件 -->
<header-component @refresh="handleRefresh" />
<list-component :refresh-flag="refreshFlag" />
</div>
</template>
 
<script setup lang="ts">
import { ref } from 'vue'
import HeaderComponent from './HeaderComponent.vue'
import ListComponent from './ListComponent.vue'
 
const refreshFlag = ref(0)
 
const handleRefresh = () => {
// 修改标记触发子组件更新
refreshFlag.value++
}
</script>
 
vue
// HeaderComponent.vue
 
<template>
<div class="header">
<a-button @click="refresh">刷新数据</a-button>
</div>
</template>
 
<script setup lang="ts">
const emit = defineEmits(['refresh'])
 
const refresh = () => {
emit('refresh')
}
</script>
 
vue
// ListComponent.vue
 
<template>
<div class="list">
<a-table :data-source="dataList" :columns="columns" />
</div>
</template>
 
<script setup lang="ts">
import { ref, watch } from 'vue'
 
const props = defineProps({
refreshFlag: {
type: Number,
default: 0
}
})
 
const dataList = ref([])
 
// 监听 refreshFlag 变化
watch(() => props.refreshFlag, () => {
fetchData()
})
 
const fetchData = async () => {
// 实际开发中这里调用接口
// const res = await api.getList()
// dataList.value = res.data
}
</script>
 

二、Provide/Inject 方式(跨多层组件)

适用于深层组件嵌套的场景:

vue
// AppRoot.vue
 
<template>
<div>
<header-component />
<sidebar-component>
<list-component />
</sidebar-component>
</div>
</template>
 
<script setup lang="ts">
import { provide, ref } from 'vue'
 
// 提供更新函数和状态
const refreshFlag = ref(0)
const triggerRefresh = () => {
refreshFlag.value++
}
 
provide('listRefresh', {
refreshFlag,
triggerRefresh
})
</script>
 
vue
// HeaderComponent.vue
 
<template>
<a-button @click="refresh">刷新列表</a-button>
</template>
 
<script setup lang="ts">
import { inject } from 'vue'
 
// 注入更新函数
const { triggerRefresh } = inject('listRefresh')
 
const refresh = () => {
triggerRefresh()
}
</script>
 
vue
// ListComponent.vue
 
<template>
<a-table :data-source="dataList" :columns="columns" />
</template>
 
<script setup lang="ts">
import { inject, watch } from 'vue'
 
// 注入更新标记
const { refreshFlag } = inject('listRefresh')
 
const dataList = ref([])
 
watch(() => refreshFlag.value, () => {
fetchData()
})
</script>
 

三、Event Bus(任意组件)

使用 mitt 实现事件总线:

ts
// eventBus.ts
 
import mitt from 'mitt'
export const emitter = mitt()
 
vue
// HeaderComponent.vue
 
<template>
<a-button @click="refresh">刷新列表</a-button>
</template>
 
<script setup lang="ts">
import { emitter } from '@/utils/eventBus'
 
const refresh = () => {
emitter.emit('refreshList')
}
</script>
 
vue
//ListComponent.vue
 
<template>
<a-table :data-source="dataList" :columns="columns" />
</template>
 
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { emitter } from '@/utils/eventBus'
 
const dataList = ref([])
 
const handleRefresh = () => {
fetchData()
}
 
onMounted(() => {
emitter.on('refreshList', handleRefresh)
})
 
onUnmounted(() => {
emitter.off('refreshList', handleRefresh)
})
</script>
 

四、Pinia 状态管理(全局状态)

适用于需要全局管理的数据:

vue
// list.ts
 
import { defineStore } from 'pinia'
 
export const useListStore = defineStore('list', {
state: () => ({
refreshFlag: 0,
listData: []
}),
actions: {
triggerRefresh() {
this.refreshFlag++
},
async fetchListData() {
// const res = await api.getList()
// this.listData = res.data
}
}
})
 
vue
// HeaderComponent.vue
 
<template>
<a-button @click="refresh">刷新列表</a-button>
</template>
 
<script setup lang="ts">
import { useListStore } from '@/stores/list'
 
const listStore = useListStore()
 
const refresh = () => {
listStore.triggerRefresh()
}
</script>
 
vue
// ListComponent.vue
 
<template>
<a-table :data-source="listStore.listData" :columns="columns" />
</template>
 
<script setup lang="ts">
import { useListStore } from '@/stores/list'
import { storeToRefs } from 'pinia'
 
const listStore = useListStore()
const { refreshFlag } = storeToRefs(listStore)
 
watch(() => refreshFlag.value, () => {
listStore.fetchListData()
})
</script>
 

五、组件实例方法(父组件调用子组件)

通过 ref 直接调用子组件方法:

vue
// ParentComponent.vue
 
<template>
<div>
<a-button @click="refreshList">刷新列表</a-button>
<list-component ref="listRef" />
</div>
</template>
 
<script setup lang="ts">
import { ref } from 'vue'
 
const listRef = ref()
 
const refreshList = () => {
listRef.value?.refresh()
}
</script>
 
vue
// ListComponent.vue
 
<template>
<a-table :data-source="dataList" :columns="columns" />
</template>
 
<script setup lang="ts">
const dataList = ref([])
 
// 暴露给父组件的方法
defineExpose({
refresh: () => {
fetchData()
}
})
</script>
 

选择建议

1.父子组件通信:优先使用 Props + Emit

2.跨多层组件:使用 Provide/Inject

3.简单的全局通信:使用 Event Bus

4.复杂的状态管理:使用 Pinia

5.父组件调用子组件方法:使用 ref + expose

注意事项

1.Props + Emit 要注意避免过度使用,以防产生"prop 钻取"

2.Event Bus 要记得及时解除事件监听

3.Provide/Inject 建议提供响应式数据

4.Pinia 适合管理全局共享的数据

5.ref 调用子组件方法要注意可能的空值情况

总结

选择合适的通信方式要根据具体场景:

1.组件关系(父子、兄弟、跨层级)

2.数据复杂度

3.是否需要全局管理

4.组件复用性要求

合理使用这些通信方式,可以让我们的代码更加清晰和易维护。

Preview

8

点个赞 ~

版权申明: © 本文著作权归YGHub所有,未经YGHub网授权许可,禁止第三方以任何形式转载和使用本文内容。