Vue3全局配置Loading的完整指南:从基础到实战
引言:
在现代Web应用开发中,加载状态的管理直接影响用户体验。本文将系统介绍Vue3中实现全局Loading的三种主流方案,包括基于Pinia的状态管理方案、全局API挂载方案以及使用Ant Design Vue组件库的集成方案,并提供详细的实现步骤和最佳实践。
为什么需要全局Loading?
- 用户体验优化:避免用户重复操作和等待焦虑
- 操作状态统一:在复杂业务流程中保持一致的加载状态
- 代码复用:减少重复开发,提高维护效率
- 错误边界处理:网络异常时提供清晰的状态反馈
实现方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Pinia状态管理 | 状态可控性强,支持复杂场景 | 实现相对复杂 | 中大型应用、状态复杂的场景 |
| 全局API挂载 | 轻量灵活,使用简单 | 缺乏状态管理能力 | 小型应用、快速原型开发 |
| 第三方UI库 | 风格统一,功能完善 | 增加依赖体积 | 已使用UI库的项目 |
方案一:基于Pinia的全局Loading实现
1. 创建Loading状态管理
// src/store/modules/loading.ts
import { defineStore } from 'pinia'
export const useLoadingStore = defineStore('loading', {
state: () => ({
isLoading: false,
count: 0 // 用于处理并发请求
}),
actions: {
showLoading() {
this.count++
this.isLoading = true
},
hideLoading() {
if (this.count > 0) {
this.count--
if (this.count === 0) {
this.isLoading = false
}
}
}
}
})
2. 注册Pinia并创建全局组件
// src/main.ts
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
// src/***ponents/GlobalLoading.vue
<template>
<div v-if="isLoading" class="loading-overlay">
<div class="spinner"></div>
<p class="loading-text">{{ loadingText }}</p>
</div>
</template>
<script setup>
import { useLoadingStore } from '@/store/modules/loading'
import { ***puted } from 'vue'
const loadingStore = useLoadingStore()
const isLoading = ***puted(() => loadingStore.isLoading)
const loadingText = ***puted(() => loadingStore.text || '加载中...')
</script>
<style scoped>
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: white;
margin-top: 1rem;
font-size: 1.2rem;
}
</style>
3. 在App.vue中引入
<template>
<router-view />
<GlobalLoading />
</template>
<script setup>
import GlobalLoading from '@/***ponents/GlobalLoading.vue'
</script>
4. 在HTTP请求中自动使用
// src/utils/http.ts
import { useLoadingStore } from '@/store/modules/loading'
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
const requestConfig = config as RequestConfig;// 二次封装的请求参数
if (requestConfig?.showLoading) {
const loadingStore = useLoadingStore()
loadingStore.showLoading()
}
return config
},
(error) => {
const loadingStore = useLoadingStore()
loadingStore.hideLoading()
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
const loadingStore = useLoadingStore()
loadingStore.hideLoading()
return response
},
(error) => {
const loadingStore = useLoadingStore()
loadingStore.hideLoading()
return Promise.reject(error)
}
)
方案二:全局API挂载实现
1. 创建Loading服务
// src/utils/loading.ts
import { createVNode, render, App } from 'vue'
import Loading***ponent from './Loading.vue'
let loadingInstance: any = null
const container = document.createElement('div')
export const LoadingService = {
install(app: App) {
// 注册全局方法
app.config.globalProperties.$loading = {
show: (options = {}) => {
if (loadingInstance) {
this.hide()
}
const vnode = createVNode(Loading***ponent, options)
render(vnode, container)
document.body.appendChild(container.firstElementChild!)
loadingInstance = vnode.***ponent
},
hide: () => {
if (loadingInstance) {
render(null, container)
loadingInstance = null
}
}
}
}
}
2. 在组件中使用
// src/views/Register/index.vue
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()!
const handleSubmit = async () => {
try {
proxy.$loading.show({
text: '注册中,请稍候...'
})
await registerUser(formData)
proxy.$router.push('/login')
} catch (error) {
console.error('注册失败', error)
} finally {
proxy.$loading.hide()
}
}
</script>
方案三:使用Ant Design Vue实现(UI库)
1. 安装依赖
npm install ant-design-vue @ant-design/icons-vue --save
2. 全局配置
import { createApp } from 'vue'
import App from './App.vue'
import { Spin } from 'ant-design-vue'
import { LoadingOutlined } from '@ant-design/icons-vue'
import 'ant-design-vue/dist/reset.css'
const app = createApp(App)
// 注册组件
app.***ponent(Spin.name, Spin)
app.***ponent(LoadingOutlined.name, LoadingOutlined)
// 挂载全局Loading方法
app.config.globalProperties.$loading = {
show: (options = {}) => {
const container = document.createElement('div')
document.body.appendChild(container)
const loadingInstance = createVNode(Spin, {
size: 'large',
spinning: true,
tip: options.tip || '加载中...',
indicator: createVNode(LoadingOutlined, { spin: true }),
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
background: 'rgba(0, 0, 0, 0.5)',
zIndex: 9999,
...options.style
}
})
render(loadingInstance, container)
return () => {
render(null, container)
document.body.removeChild(container)
}
}
}
app.mount('#app')
3. 结合HTTP拦截器使用
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()!
// 请求拦截器
service.interceptors.request.use(
(config) => {
config.loadingHide = proxy.$loading.show({
tip: '数据加载中...'
})
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
response.config.loadingHide()
return response
},
(error) => {
error.config.loadingHide()
return Promise.reject(error)
}
)
三种方案对比与选择建议
Pinia方案
✅ 优点:状态管理清晰,支持复杂场景,适合大型应用
❌ 缺点:需要引入Pinia,配置相对复杂 💡 适用场景:中大型企业级应用,需要精细控制加载状态
全局API方案
✅ 优点:轻量级,无依赖,实现简单
❌ 缺点:状态管理较弱,不适合复杂场景 💡 适用场景:小型应用,快速原型开发
Ant Design Vue方案
✅ 优点:UI一致性好,功能完善,自带主题
❌ 缺点:增加第三方依赖体积 💡 适用场景:已使用Ant Design Vue的项目
高级特性扩展
1. 加载状态防抖
// 在loading.ts中添加
show: (options = {}) => {
// 设置最小显示时间,避免闪烁
const { minDuration = 300 } = options
const startTime = Date.now()
// ... 原有代码 ...
return () => {
const endTime = Date.now()
const duration = endTime - startTime
if (duration < minDuration) {
setTimeout(() => {
render(null, container)
loadingInstance = null
}, minDuration - duration)
} else {
render(null, container)
loadingInstance = null
}
}
}
2. 支持多实例和局部加载
// 创建局部加载方法
showLocal: (target: HTMLElement, options = {}) => {
const vnode = createVNode(Loading***ponent, options)
render(vnode, container)
target.appendChild(container.firstElementChild!)
return () => {
render(null, container)
}
}
总结
全局Loading作为提升用户体验的关键功能,在Vue3中有多种实现方式。选择方案时应根据项目规模、团队技术栈和业务需求综合考量。小型项目可选择轻量级的全局API方案,中大型项目推荐使用Pinia方案以获得更好的状态管理能力,而已使用UI库的项目则应优先考虑集成方案以保持风格统一。
通过本文介绍的方法,你可以构建出灵活、高效且用户友好的全局加载状态管理系统,为你的Vue3应用提供专业级的用户体验。
如果你有更优的办法,可以评论区探讨一下!^v^