使用MUI Loading Button时,onClick中调用异步函数导致加载状态不更新的原因及解决方法
你遇到的核心问题是React状态更新的异步批量特性,再加上代码里状态设置的时机冲突:
React useState更新是异步的:当你在
onClick回调里调用setDownloadLoading(true)后,React并不会立刻修改downloadLoading的值——这个更新会被放入队列,等到当前事件循环的所有同步代码执行完毕后才会处理。所以在紧接着的console.log(downloadLoading)和downloadFile调用中,你拿到的依然是更新前的false值,这是React的正常行为。状态被快速覆盖:你的
downloadFile函数最后不管请求成功还是失败,都会调用setDownloadLoading(false)。如果你的fetch请求完成得很快(比如本地服务响应极快),setDownloadLoading(false)会在setDownloadLoading(true)的状态更新还没来得及渲染到组件上时就执行,导致加载状态刚要生效就被取消,看起来像是状态始终为false。
另外,你代码里那个空依赖的useEffect其实没什么实际作用(它只会在组件卸载时把状态设为false),反而可能在某些场景下干扰状态逻辑,建议直接移除。
最直接的修复方式是把setDownloadLoading(true)的调用移到downloadFile函数内部,确保状态设置和异步请求的流程完全同步:
export const Row: React.FC<IProps> = ({ record }: IProps): JSX.Element => { const [downloadLoading, setDownloadLoading] = useState<boolean>(false); const download = (content: any, fileName: string) => { const link = document.createElement("a"); link.setAttribute("href", URL.createObjectURL(content)); link.setAttribute("download", `${fileName}.pdf`); link.style.visibility = "hidden"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const downloadFile = async ( e: React.MouseEvent<HTMLElement>, id: string | undefined, fileName: string ) => { e.preventDefault(); // 进入函数后立刻设置加载状态为true setDownloadLoading(true); try { const response = await fetch( `http://localhost:8080/download/id/` + id ); if (!response.ok) { const value = await response.json(); console.log("val", value); } else { const content = await response.blob(); download(content, fileName); } } catch (e) { console.log("error", e); } finally { // 不管请求成功/失败,最后都统一关闭加载状态 setDownloadLoading(false); } }; return ( <> <TableRow> <TableCell align="left" size="medium"> {record.name} </TableCell> <TableCell size="small"> <OutlinedCustomButton loading={downloadLoading} loadingIndicator={<CircularProgress color="inherit" size={20} />} loadingPosition="end" endIcon={<FileDownloadIcon />} fullWidth onClick={(e) => { downloadFile(e, record.id, record.name); }} > Download </OutlinedCustomButton> </TableCell> </TableRow> </> ); };
额外优化点
- 把
response.json().then(...)改成await response.json(),保持async/await的语法一致性,避免混合使用Promise链式调用。 - 用
finally块统一处理状态重置,确保不管请求成功还是失败,加载状态都会被关闭,避免出现按钮一直处于加载状态的情况。
这样修改后,点击按钮时会立刻触发加载状态,MUI的Loading Button会正常显示加载指示器,等到异步请求完成后再自动关闭加载状态,完全符合你的预期。
内容的提问来源于stack exchange,提问作者Enchantres

