uni-app——uni-app列表刷新终极指南:告别onShow无脑请求,一套优雅的通信方案

张开发
2026/4/7 14:52:44 15 分钟阅读

分享文章

uni-app——uni-app列表刷新终极指南:告别onShow无脑请求,一套优雅的通信方案
本文复盘一个Bug用户在列表页新增数据后返回列表却不显示新内容必须反复切换 Tab 才会出现。根因是页面间缺乏刷新通知机制。问题一新增数据后返回列表看不到新数据问题现象小程序中有一个带 Tab 切换的列表页分类一 / 分类二。用户操作流程点击「新增」按钮进入创建页填写信息提交成功返回列表页 ——列表中看不到刚创建的数据更诡异的是切换到分类二再切回分类一数据偶尔会出现再创建一条数据后前一条和这一条一起出现问题代码vue!-- list.vue列表页-- script setup import { ref } from vue; import { onLoad, onShow } from dcloudio/uni-app; const listRef ref(null); onLoad(() { listRef.value?.loadData(); // 首次加载正常 }); onShow(() { // ❌ 问题空的从创建页返回时什么都没做 }); /scriptvue!-- add.vue创建页-- script setup const handleSubmit async () { const res await createItemApi(formData); if (res) { uni.showToast({ title: 创建成功 }); setTimeout(() { uni.navigateBack(); // ❌ 问题只返回没通知列表刷新 }, 1500); } }; /script根本原因在 uni-app / 小程序中navigateBack()返回时只触发列表页的onShow()不触发onLoad()。而原代码的onShow()是空的没有任何刷新逻辑。创建页也没有任何通知对方刷新的机制。解决方案事件总线 按需刷新核心思路使用uni.$emit/uni.$on建立页面间通信。修复后的列表页vue!-- list.vue -- template view !-- Tab 切换 -- view classtabs view v-for(tab, idx) in [分类一, 分类二] :keyidx :class{ active: activeTab idx } clickactiveTab idx {{ tab }} /view /view !-- 列表组件 -- data-list reflistARef v-showactiveTab 0 :apifetchListA / data-list reflistBRef v-showactiveTab 1 :apifetchListB / /view /template script setup import { ref, nextTick } from vue; import { onLoad, onShow, onUnload } from dcloudio/uni-app; const activeTab ref(0); const listARef ref(null); const listBRef ref(null); const needRefresh ref(false); // ✅ 修复点 1监听刷新事件 onLoad(() { uni.$on(list-refresh, (data) { if (data?.tab ! undefined) activeTab.value data.tab; needRefresh.value true; // 只打标记不立即刷新 }); }); // ✅ 修复点 2页面可见时执行刷新 onShow(() { if (needRefresh.value) { needRefresh.value false; refreshCurrentTab(); } }); // ✅ 修复点 3移除监听防止内存泄漏 onUnload(() { uni.$off(list-refresh); }); const refreshCurrentTab () { nextTick(() { const targetRef activeTab.value 0 ? listARef : listBRef; targetRef.value?.loadData(true); }); }; /script修复后的创建页vue!-- add.vue -- script setup const handleSubmit async () { const res await createItemApi(formData); if (res) { uni.showToast({ title: 创建成功 }); setTimeout(() { // ✅ 修复点发送刷新事件 uni.$emit(list-refresh, { tab: 0 }); uni.navigateBack(); }, 1500); } }; /script问题二为什么不在事件回调里直接刷新错误写法javascript// ❌ 看似简单实际有问题 uni.$on(list-refresh, () { refreshCurrentTab(); // 此时列表页可能还没渲染完 });问题原因uni.$emit是同步的在navigateBack()之前就会触发。此时列表页虽然在页面栈中但页面切换动画可能还没完成DOM 可能还没更新组件的 ref 可能还是null正确做法采用延迟执行模式步骤动作时机1事件回调中设置needRefresh trueuni.$emit触发时2onShow()中检查标记并执行刷新页面真正可见时问题三切换用户身份后返回数据不对问题场景实际项目中列表数据通常和当前用户所在的团队/空间绑定。如果用户在其他页面切换了团队返回列表页时也需要刷新。解决方案检测上下文变化javascriptconst lastContextId ref(null); onShow(() { const currentContextId userStore.contextId; // 检测上下文是否切换 if (lastContextId.value ! null lastContextId.value ! currentContextId) { needRefresh.value true; } lastContextId.value currentContextId; // 按需刷新 if (needRefresh.value) { needRefresh.value false; refreshCurrentTab(); } });覆盖两种刷新场景场景触发方式新增/编辑数据后返回事件总线设置标记切换团队/空间后返回对比 contextId 检测变化方案对比为什么选事件总线方案优点缺点适用场景事件总线本文方案简单直接无额外依赖需手动管理注册/注销✅ 新增后刷新onShow无条件刷新最简单浪费请求丢失滚动位置❌ 不推荐EventChannel官方推荐navigateBack时取 channel 麻烦正向传参Pinia/Vuex统一管理过度设计复杂状态同步结论对于新增数据后刷新列表这种场景事件总线是最佳平衡点——足够简单又避免了无条件刷新的性能浪费。完整 Demo 代码可复用列表组件vue!-- components/data-list.vue -- template scroll-view scroll-y scrolltolowerloadMore view v-foritem in list :keyitem.id slot :itemitem / /view view v-ifloading加载中.../view view v-iffinished没有更多了/view /scroll-view /template script setup import { ref } from vue; const props defineProps({ api: { type: Function, required: true }, }); const list ref([]); const page ref(1); const loading ref(false); const finished ref(false); const loadData async (reset false) { if (reset) { page.value 1; list.value []; finished.value false; } if (loading.value || finished.value) return; loading.value true; try { const res await props.api({ page: page.value, pageSize: 20 }); const newList res?.data?.list || []; list.value reset ? newList : [...list.value, ...newList]; if (newList.length 20) finished.value true; page.value; } finally { loading.value false; } }; const loadMore () loadData(false); defineExpose({ loadData }); /script列表页完整示例vue!-- pages/list.vue -- template view view classtabs view v-for(tab, idx) in [分类一, 分类二] :keyidx :class{ active: activeTab idx } clickactiveTab idx {{ tab }} /view /view data-list reflistOneRef v-showactiveTab 0 :apifetchListOne template #default{ item } view classcard{{ item.name }}/view /template /data-list data-list reflistTwoRef v-showactiveTab 1 :apifetchListTwo template #default{ item } view classcard{{ item.name }}/view /template /data-list view classfab clickgoAdd/view /view /template script setup import { ref, watch, nextTick } from vue; import { onLoad, onShow, onUnload } from dcloudio/uni-app; import { fetchListOne, fetchListTwo } from /api/demo; const activeTab ref(0); const listOneRef ref(null); const listTwoRef ref(null); const needRefresh ref(false); onLoad(() { refreshCurrentTab(); uni.$on(data-refresh, (data) { if (data?.tab ! undefined) activeTab.value data.tab; needRefresh.value true; }); }); onShow(() { if (needRefresh.value) { needRefresh.value false; refreshCurrentTab(); } }); onUnload(() { uni.$off(data-refresh); }); watch(activeTab, () { nextTick(() refreshCurrentTab()); }); const refreshCurrentTab () { nextTick(() { const targetRef activeTab.value 0 ? listOneRef : listTwoRef; targetRef.value?.loadData(true); }); }; const goAdd () { const url activeTab.value 0 ? /pages/add-one : /pages/add-two; uni.navigateTo({ url }); }; /script创建页完整示例vue!-- pages/add-one.vue -- script setup import { ref } from vue; import { createItemApi } from /api/demo; const formData ref({ name: , desc: }); const handleSubmit async () { if (!formData.value.name) { return uni.showToast({ title: 请填写名称, icon: none }); } try { const res await createItemApi(formData.value); if (res) { uni.showToast({ title: 创建成功, icon: success }); setTimeout(() { uni.$emit(data-refresh, { tab: 0 }); uni.navigateBack(); }, 1500); } } catch (err) { uni.showToast({ title: err.message || 创建失败, icon: none }); } }; /script经验总结要点说明navigateBack不触发onLoad回到已有页面只触发onShowonShow≠ 需要刷新用标记区分场景避免无效请求uni.$emit是同步的不要在回调里直接操作 DOMuni.$on必须配对uni.$off在onUnload中移除防止重复注册刷新时机要在onShow里确保页面可见后再更新 UI一句话总结小程序navigateBack()只触发onShow而非onLoad。新增数据后列表不刷新本质是页面间缺少通信机制。通过uni.$emit/uni.$on事件总线 needRefresh标记位既能精确控制刷新时机又避免了无条件刷新带来的性能浪费。

更多文章