如何在React中实现API请求触发的连续流畅进度加载条
问题原因
你的代码遇到了React的闭包陷阱:changePercent函数在首次触发时,捕获的是当时的loading、loadingPercent状态快照,后续状态更新后,函数内部拿到的还是旧的初始值,所以只会执行一次setPercent(1),后续调用因为判断的旧值永远符合条件但不会更新到新的进度,看起来就卡住了。同时你没有对requestAnimationFrame的回调做清理,也会导致内存泄漏和异常执行。
修复方案
1. 解决闭包与重复调用问题
用useRef存储最新的状态和回调ID,避免闭包捕获旧值,同时添加清理逻辑:
import { useRef, useEffect, useState } from 'react' // 你的组件内逻辑 const [loadingPercent, setPercent] = useState(0) // 用ref存最新状态,避免闭包问题 const loadingRef = useRef(loading) const rafIdRef = useRef(null) // 同步loading状态到ref useEffect(() => { loadingRef.current = loading }, [loading]) useEffect(() => { if (loading) { setPercent(0) changePercent() } else { // 请求结束直接拉满进度,给用户完成反馈 setPercent(100) // 清理动画帧回调 if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current) // 可选:300ms后隐藏进度条,方便用户看到完成状态 setTimeout(() => { // 你的进度条隐藏逻辑 }, 300) } // 组件卸载时清理回调,避免内存泄漏 return () => { if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current) } }, [loading]) const changePercent = () => { // 用ref的最新状态判断 if (!loadingRef.current) return // 函数式更新percent,不依赖外部状态 setPercent(prev => { return prev < 99 ? prev + 1 : 0 }) // 存储回调ID方便后续清理 rafIdRef.current = requestAnimationFrame(changePercent) }
2. 实现流畅的连续动画效果
如果要做到和示例一致的丝滑效果,不要固定每次加1,而是根据时间差计算增量,适配不同设备的刷新率,同时可以调整增速逻辑,越接近99%增速越慢,更符合真实请求的感知:
// 新增时间记录ref const lastTimeRef = useRef(Date.now()) const changePercent = () => { if (!loadingRef.current) return const now = Date.now() // 计算两次动画帧的时间差 const deltaTime = now - lastTimeRef.current lastTimeRef.current = now // 按10秒走完0-99的速度计算增量,可自行调整系数修改速度 const increment = (deltaTime / 10000) * 99 setPercent(prev => { const newVal = prev + increment // 最高到99%,请求结束后再拉满到100% return newVal >= 99 ? 99 : newVal }) rafIdRef.current = requestAnimationFrame(changePercent) }
内容的提问来源于stack exchange,提问作者devb
相关产品推荐
相关产品推荐

