教程 axios tutorial

结合拦截器实现全局加载提示

在实际开发中,请求发出与响应返回之间常有延迟。为了提升用户体验,可以在 Axios 拦截器中实现全局加载提示(loading)。下面给出思路与多种实现方式(Vue3 + Element Plus 示例、React + 全局状态示例),并说明常见注意点。

思路概述

  1. 请求开始时显示加载动画:请求拦截器中维护活跃请求计数,当计数由 0 → 1 时显示 loading。
  2. 请求结束时隐藏加载动画:响应拦截器或错误处理时递减计数,当计数为 0 时关闭 loading。
  3. 支持按请求级别关闭 loading:通过 config.hideLoading 控制单次请求不触发全局 loading。
  4. 支持防抖显示(可选):对短请求避免闪烁,设置最小显示时长或延迟显示。

Vue3 + Element Plus 示例(单文件封装)

js
// src/utils/request.js
import axios from 'axios';
import { ElLoading, ElMessage } from 'element-plus';

let loadingInstance = null;
let requestCount = 0;
const MIN_SHOW_TIME = 300; // 最短展示时间,避免闪烁
let showTimer = null;
let shownAt = 0;

function showLoading() {
  if (requestCount === 0) {
    // 延迟显示以避免短请求闪烁
    showTimer = setTimeout(() => {
      loadingInstance = ElLoading.service({
        lock: true,
        text: '加载中...',
        background: 'rgba(0,0,0,0.3)',
      });
      shownAt = Date.now();
    }, 150); // 150ms 延迟显示
  }
  requestCount++;
}

function hideLoading() {
  requestCount = Math.max(0, requestCount - 1);
  if (requestCount === 0) {
    // 若已经显示,保证至少显示 MIN_SHOW_TIME 毫秒
    clearTimeout(showTimer);
    const elapsed = Date.now() - shownAt;
    const delay = elapsed < MIN_SHOW_TIME ? MIN_SHOW_TIME - elapsed : 0;
    setTimeout(() => {
      loadingInstance?.close?.();
      loadingInstance = null;
      shownAt = 0;
    }, delay);
  }
}

const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
  timeout: 10000,
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    if (!config.hideLoading) showLoading();
    const token = localStorage.getItem('token');
    if (token) config.headers.Authorization = `Bearer ${token}`;
    return config;
  },
  (error) => {
    hideLoading();
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    hideLoading();
    return response;
  },
  (error) => {
    hideLoading();
    // 可在此做统一错误提示
    ElMessage.error(error.response?.data?.message || error.message || '请求失败');
    return Promise.reject(error);
  }
);

export default service;

使用方式(在组件中)

js
import request from '@/utils/request';

// 默认会触发全局 loading
request.get('/users').then(res => console.log(res.data));

// 某些请求不想显示 loading,可设置 hideLoading
request.get('/status', { hideLoading: true }).then(res => console.log(res.data));

React + 全局状态(Redux / Context)示例

js
// store/loadingSlice.js (Redux Toolkit 示例)
import { createSlice } from '@reduxjs/toolkit';

const loadingSlice = createSlice({
  name: 'loading',
  initialState: { count: 0, visible: false },
  reducers: {
    start(state) {
      state.count++;
      state.visible = state.count > 0;
    },
    end(state) {
      state.count = Math.max(0, state.count - 1);
      state.visible = state.count > 0;
    },
    reset(state) {
      state.count = 0;
      state.visible = false;
    }
  }
});

export const { start, end, reset } = loadingSlice.actions;
export default loadingSlice.reducer;
js
// utils/request.js
import axios from 'axios';
import store from '@/store';
import { start, end } from '@/store/loadingSlice';

let requestCount = 0;

function show() {
  if (requestCount === 0) store.dispatch(start());
  requestCount++;
}
function hide() {
  requestCount = Math.max(0, requestCount - 1);
  if (requestCount === 0) store.dispatch(end());
}

const api = axios.create({ baseURL: '/api' });

api.interceptors.request.use(config => {
  if (!config.hideLoading) show();
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
}, err => { hide(); return Promise.reject(err); });

api.interceptors.response.use(res => { hide(); return res; }, err => { hide(); return Promise.reject(err); });

export default api;

在 React 顶层渲染一个 LoadingOverlay,当 loading.visible 为 true 时显示全局加载组件(如 <Spin /> 或自定义遮罩)。

常见注意点与优化建议

  1. 避免闪烁:对短请求使用延迟显示或最短显示时间(如上示例)。
  2. 并发请求:使用计数器避免多个并发请求导致重复 hide/show。
  3. 按需关闭:支持 config.hideLoading 或 config.showLoading = false 控制单次请求不触发全局 loading。
  4. 错误场景:在请求异常或拦截器内部抛错时务必调用 hide 减少加载残留。
  5. SSR 考虑:在服务端渲染场景避免直接调用浏览器 UI API(如 ElLoading),需在客户端渲染时初始化。
  6. 可复用性:将 show/hide/计数器封装为独立模块,方便在不同项目中复用。

小结

通过在 Axios 拦截器中维护活跃请求计数,并结合 UI 提示组件或全局状态,可以实现可靠且用户友好的全局加载提示。合理处理延迟显示、最短显示时长与单次请求控制,能显著提升用户体验并避免 UI 闪烁或残留问题。