共计 2768 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点
在复杂的前端应用中,全局网络请求进度条的实现常常面临几个典型问题:

- 微前端架构下的状态隔离:子应用独立运行时可能重复注册拦截器,导致进度事件多次触发
- SSR 场景的 hydration 冲突:服务端已完成的请求在客户端重新触发进度条动画
- 多 Tab 竞态问题:浏览器后台 Tab 的长时间请求返回后,错误更新前台进度状态
传统解决方案往往简单地在每个请求开始时显示进度条,结束时隐藏,这种粗暴的方式会导致:
- 高频请求时进度条频繁闪烁
- 并发请求时进度突然从 20% 跳到 100%
- 异常请求中断后进度条僵死
技术方案
核心架构设计
我们采用三层控制模型:
- 拦截器层:改造 axios 实例,添加请求 / 响应拦截
- 状态机层:管理当前所有活动的请求状态
- 视图层 :通过 ArcoDesign 的
GlobalLoading组件渲染进度
关键实现步骤
1. 请求拦截器封装
// 创建增强型 axios 实例
const createEnhancedAxios = () => {const instance = axios.create();
// 请求拦截器
instance.interceptors.request.use(config => {const requestId = generateRequestId(config);
store.dispatch(addRequest({
id: requestId,
weight: calculateRequestWeight(config),
startedAt: Date.now()}));
return {...config, meta: { requestId} };
});
// 响应拦截器
instance.interceptors.response.use(
response => {store.dispatch(completeRequest(response.config.meta.requestId));
return response;
},
error => {if (axios.isCancel(error)) {store.dispatch(cancelRequest(error.config.meta.requestId));
} else {store.dispatch(failRequest(error.config.meta.requestId));
}
return Promise.reject(error);
}
);
return instance;
};
2. 请求队列管理
采用权重分配算法处理不同类型请求:
- 静态资源请求:权重 0.3
- API 接口请求:权重 0.7
- 大文件上传:权重 1.0
// 状态机核心逻辑
const progressReducer = (state, action) => {switch (action.type) {
case 'ADD_REQUEST':
return {
...state,
activeRequests: [...state.activeRequests, action.payload],
totalWeight: state.totalWeight + action.payload.weight
};
case 'COMPLETE_REQUEST':
return updateProgress(state, action.payload, 'completed');
// 其他 action 处理...
}
};
// 进度计算公式
const calculateProgress = (state) => {
const completedWeight = state.completedRequests
.reduce((sum, r) => sum + r.weight, 0);
return Math.min(completedWeight / state.totalWeight * 100, 99); // 保留 1% 给页面渲染
};
3. 异常处理策略
- 超时中断:通过 AbortController 取消请求并回滚进度
- 错误降级:当连续 3 个请求失败时自动隐藏进度条
- 重试机制:对 5xx 错误自动重试并保持进度连续
完整代码实现
ProgressProvider 高阶组件
interface ProgressState {
visible: boolean;
percent: number;
}
const ProgressProvider: React.FC = ({children}) => {const [state, setState] = useState<ProgressState>({
visible: false,
percent: 0
});
// 使用 useContext 订阅 store 变化
const storeState = useStore(selector);
useEffect(() => {const { activeRequests, progress} = storeState;
setState({
visible: activeRequests.length > 0,
percent: progress
});
}, [storeState]);
return (
<>
<GlobalLoading
loading={state.visible}
percent={state.percent}
aria-busy={state.visible}
/>
{children}
</>
);
};
防御性编程要点
- 请求 ID 生成:结合 URL、method、timestamp 创建唯一标识
- 权重溢出处理:当 totalWeight 超过阈值时自动归一化
- 内存清理:设置最大请求记录数(建议 50 条),避免内存泄漏
性能优化
渲染策略对比
| 方案 | LCP 影响 | CPU 占用 | 适用场景 |
|---|---|---|---|
| 实时更新 | +30ms | 高 | 后台管理系统 |
| 防抖(200ms) | +5ms | 中 | 移动端页面 |
| 分段更新 | +10ms | 低 | 数据看板 |
Web Vitals 优化建议
- 在 CLS(Cumulative Layout Shift)敏感页面禁用进度条动画
- 对 LCP(Largest Contentful Paint)关键请求使用零权重
- 使用 will-change: opacity 提升动画性能
避坑指南
生产环境常见问题
- CDN 资源加载缺失
- 解决方案:通过
<link rel="preload">预加载关键资源 -
监控方案:使用 Resource Timing API 补全进度
-
302 重定向进度跳跃
- 保持原始请求 ID 跟随重定向链
-
在最终响应时统一更新进度
-
内存泄漏检测
- 开发环境使用 why-did-you-render 检查
- 生产环境通过 window.performance.memory 监控
开放性问题
如何设计支持动态权重的分段式进度条?考虑以下方向:
- 根据响应头中的 Content-Length 动态调整权重
- 对大文件上传实现分片进度反馈
- 结合 WebSocket 实现实时数据传输进度
这个方案已经在我们的中台系统中稳定运行 6 个月,日均处理 20 万 + 请求。关键在于平衡进度准确性和性能损耗,希望这些实践经验对你有帮助。
正文完
发表至: 前端开发
近一天内
