在 Vue 项目中,管理多个异步操作的 Loading 状态常常令人头疼。
一个页面如果需要调用三个接口,你是否还在这样写?
const loading1 = ref(false)
const loading2 = ref(false)
const loading3 = ref(false)
// 然后三个地方分别控制
这种方式过于原始。要么导致全屏 Loading 频繁闪烁,要么在页面上留下多个独立的 Loading 状态,影响体验。
本文将介绍一个在实际项目中验证过的方案,它基于 Pinia 实现,核心代码约百行,却能显著提升 Loading 管理的优雅度与健壮性。
核心思路:组件上报状态,Store 统一调度
设想这样一个场景:每个组件只需管理自身的 Loading 状态,并向一个全局 Store “上报”自己的加载情况。由 Store 统一监听所有组件的状态,只要还有任意一个组件在加载,就维持全屏 Loading 的展示。
这种设计带来了几个明显的好处:
- 组件逻辑保持简洁,无需关心其他部分的加载状态。
- 多个并发请求会被自动合并,只展示一次全局 Loading。
- 组件卸载时能自动清理状态,避免潜在的内存泄漏问题。
代码实现:如何构建全局 Loading Store
首先来看 Store 部分的实现,这是整个方案的核心。
// loading-store.js
export const useLoadingStore = defineStore('loading', () => {
// 存储所有组件的 loading 状态源
const loadingSources = ref([])
// 组件使用此方法注册自身
const addLoadingSource = (source) => {
loadingSources.value.push(source)
}
// 组件卸载时移除自身
const removeLoadingSource = (source) => {
const index = loadingSources.value.indexOf(source)
if (index > -1) {
loadingSources.value.splice(index, 1)
}
}
// 关键计算属性:只要有一个状态源为真,即表示正在加载
const isLoading = computed(() =>
loadingSources.value.some(source => toValue(source))
)
// 监听加载状态变化,自动控制全屏 Loading 的显示与隐藏
watch(isLoading, (loading) => {
if (loading) {
// 例如:显示 Element Plus 的全屏 Loading
ElLoading.service({ fullscreen: true })
} else {
// 所有加载均完成,关闭全屏 Loading
// 此处需处理 Loading 实例的引用,示例中简化处理
}
})
return { addLoadingSource, removeLoadingSource }
})
在组件中如何使用
在组件中使用此方案变得极其简单,这得益于 Vue.js 的组合式 API 与 Pinia 的便捷性。
<script setup>
import { ref, onBeforeUnmount } from 'vue'
import { useLoadingStore } from './loading-store'
const loadingStore = useLoadingStore()
const myLoading = ref(false) // 组件自身的 loading 状态
// 向全局 Store 注册,告知“请监听我的状态”
loadingStore.addLoadingSource(myLoading)
const fetchData = async () => {
myLoading.value = true // 只需操作自身的状态
try {
await api.getData()
} finally {
myLoading.value = false // 完成后关闭
}
}
// 重要:组件卸载时务必清理注册的状态源
onBeforeUnmount(() => {
loadingStore.removeLoadingSource(myLoading)
})
</script>
可以看到,组件完全无需关心全局 Loading 如何显示或隐藏,只需专注于管理自己的 myLoading.value = true/false。其余所有协调工作都由全局 Store 负责。
方案的优势与精妙之处
-
自动合并并发请求
当页面同时发起三个请求时,三个组件会将各自的 Loading 状态设为 true,但全局全屏 Loading 只会出现一次。直到最后一个请求结束,Loading 才会消失,有效避免了闪烁。
-
组件卸载自动清理
为了避免组件销毁后其 Loading 状态仍残留于 Store 中,我们必须在 onBeforeUnmount 生命周期钩子中移除注册。这是保证内存安全的重要习惯。
-
支持多样化的状态源
示例中使用了 VueUse 提供的 toValue 工具函数,它能智能处理 ref、computed、普通值或 getter 函数等多种响应式数据源。这意味着你的 Loading 状态可以非常灵活:
// 以下形式均可被识别
const loading = ref(false)
const loading = computed(() => someCondition)
const loading = () => isLoading.value
-
强大的可扩展性
如果你需要为 Loading 添加最小显示时长(防止闪烁),或者希望区分“页面级 Loading”和“局部 Loading”,只需在全局 Store 中增加相应的逻辑即可,所有使用该方案的组件都无需修改。
总结
这套基于 Pinia 的全局 Loading 管理方案,通过“分散上报,集中调度”的设计理念,巧妙地解决了 Vue 应用中多异步状态协调的难题。它使组件代码更纯粹,并提供了良好的自动合并与内存管理机制,具有很强的实用性与可扩展性,非常适合在中大型 Vue3 项目中应用。
|