Vue3 组件通信实战,实现跨组件数据更新
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网授权许可,禁止第三方以任何形式转载和使用本文内容。
Related article
基于微信小程序实现图片压缩、裁剪、尺寸调整的实践总结
YGHub
2025-01-02
4
Vue3 作用域插槽,提升组件复用性的利器
YGHub
2024-12-03
1
Nuxt3 中使用 localStorage 的正确姿势
YGHub
2024-12-01
3
Vue3 子组件 defineExpose 暴露方法无效的三种常见场景及解决方案
YGHub
2024-11-24
7
Vue3 keep-alive 缓存机制让页面切换更流畅
YGHub
2024-11-21
2