主题
结合拦截器实现全局加载提示
在实际开发中,请求发出与响应返回之间常有延迟。为了提升用户体验,可以在 Axios 拦截器中实现全局加载提示(loading)。下面给出思路与多种实现方式(Vue3 + Element Plus 示例、React + 全局状态示例),并说明常见注意点。
思路概述
- 请求开始时显示加载动画:请求拦截器中维护活跃请求计数,当计数由 0 → 1 时显示 loading。
- 请求结束时隐藏加载动画:响应拦截器或错误处理时递减计数,当计数为 0 时关闭 loading。
- 支持按请求级别关闭 loading:通过 config.hideLoading 控制单次请求不触发全局 loading。
- 支持防抖显示(可选):对短请求避免闪烁,设置最小显示时长或延迟显示。
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 /> 或自定义遮罩)。
常见注意点与优化建议
- 避免闪烁:对短请求使用延迟显示或最短显示时间(如上示例)。
- 并发请求:使用计数器避免多个并发请求导致重复 hide/show。
- 按需关闭:支持
config.hideLoading或config.showLoading = false控制单次请求不触发全局 loading。 - 错误场景:在请求异常或拦截器内部抛错时务必调用 hide 减少加载残留。
- SSR 考虑:在服务端渲染场景避免直接调用浏览器 UI API(如 ElLoading),需在客户端渲染时初始化。
- 可复用性:将 show/hide/计数器封装为独立模块,方便在不同项目中复用。
小结
通过在 Axios 拦截器中维护活跃请求计数,并结合 UI 提示组件或全局状态,可以实现可靠且用户友好的全局加载提示。合理处理延迟显示、最短显示时长与单次请求控制,能显著提升用户体验并避免 UI 闪烁或残留问题。