Vue3全局配置Loading的完整指南:从基础到实战

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^
转载请说明出处内容投诉
CSS教程网 » Vue3全局配置Loading的完整指南:从基础到实战

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买