React异步函数执行后操作异常:权限加载后加载器无法关闭
问题描述
我使用高阶组件(HOC)渲染受保护页面,并通过Config Service检查权限,该服务包含用于获取权限的异步API调用函数。我期望在成功获取权限数组后将加载器状态设为false,但当前代码下必须刷新页面才能实现,否则加载器会无限旋转。
ProtectedRoute组件代码
import { Navigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { useInjection } from "inversify-react"; import ConfigService from "../Services/_configService"; import { useEffect, useState } from "react"; import { resolve } from "path"; function render(c: JSX.Element) { return c; } const ProtectedRoute = (Component: any, route: string) => { const currentUser: any = useSelector((state) => state); const [isLoading, setIsLoading] = useState<boolean>(true); const config = useInjection(ConfigService) useEffect(() => { setTimeout(() => { if (config.Permissions && config.Permissions.length>0) { setIsLoading(false); } }, 1000); console.log(config.Permissions) }, [config]); if (isLoading) { return ( <span className="indicator-progress" style={{ display: "block" }}> <span className="spinner-border spinner-border-sm align-middle ms-2"></span> </span> ); } return currentUser && config.Permissions && config.Permissions.includes(route) ? render(Component) : <Navigate to="/" />; }; export default ProtectedRoute;
Config Service代码
import { injectable } from 'inversify'; import "reflect-metadata"; import {get} from "../axios/index" import { API_ENDPOINTS } from '../axios/apiEndpoint'; import { AxiosResponse } from 'axios'; interface Configuration { EnableForgetPassword: string; } interface Data { configuration: Configuration[]; permissions: string[]; } interface ConfigData { data: Data; } @injectable() class ConfigService { private _permissions: string[] = [""]; private _configuration: any = {}; constructor() { this.loadConfigData(); } async loadConfigData(): Promise<void> { const response = await get(API_ENDPOINTS.CONFIG_URL).then((response:AxiosResponse<Data>)=>response) const config = response; this._permissions=config.data.permissions this._configuration=config.data.configuration } get Permissions():string[] { return this._permissions; } get Configurations():any { return this._configuration } } export default ConfigService;
问题根源与修复方案
核心问题
- React无法感知服务内部状态变化:ConfigService是固定实例,内部
_permissions更新时,React不会触发组件重渲染,导致useEffect仅执行一次,此时权限可能还未加载完成。 - setTimeout轮询不可靠:固定1秒延迟完全依赖网络速度,API响应慢则加载器无限旋转,响应快则浪费等待时间。
修复步骤
1. 改造ConfigService,添加加载完成Promise
在服务中存储权限加载的Promise,让组件可以直接监听加载完成事件:
import { injectable } from 'inversify'; import "reflect-metadata"; import {get} from "../axios/index" import { API_ENDPOINTS } from '../axios/apiEndpoint'; import { AxiosResponse } from 'axios'; interface Configuration { EnableForgetPassword: string; } interface Data { configuration: Configuration[]; permissions: string[]; } @injectable() class ConfigService { private _permissions: string[] = [""]; private _configuration: any = {}; private _permissionLoadPromise: Promise<void>; // 新增:存储加载Promise constructor() { this._permissionLoadPromise = this.loadConfigData(); // 初始化时绑定Promise } async loadConfigData(): Promise<void> { try { const response = await get(API_ENDPOINTS.CONFIG_URL) as AxiosResponse<Data>; this._permissions = response.data.permissions; this._configuration = response.data.configuration; } catch (error) { console.error('权限加载失败:', error); // 可添加错误处理逻辑,比如设置默认权限 } } get Permissions(): string[] { return this._permissions; } get Configurations(): any { return this._configuration; } // 新增:暴露加载完成的Promise waitForPermissions(): Promise<void> { return this._permissionLoadPromise; } } export default ConfigService;
2. 重写ProtectedRoute组件,移除轮询逻辑
改用等待Promise完成的方式关闭加载器,同时修正HOC的标准写法:
import { Navigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { useInjection } from "inversify-react"; import ConfigService from "../Services/_configService"; import { useEffect, useState } from "react"; // HOC标准写法:返回一个组件函数 const ProtectedRoute = (Component: any, requiredRoute: string) => { return () => { const currentUser: any = useSelector((state) => state); const [isLoading, setIsLoading] = useState<boolean>(true); const config = useInjection(ConfigService); useEffect(() => { const checkPermissions = async () => { await config.waitForPermissions(); setIsLoading(false); // 权限加载完成后关闭加载器 }; checkPermissions(); }, [config, requiredRoute]); if (isLoading) { return ( <span className="indicator-progress" style={{ display: "block" }}> <span className="spinner-border spinner-border-sm align-middle ms-2"></span> </span> ); } const hasAccess = currentUser && config.Permissions?.includes(requiredRoute); return hasAccess ? <Component /> : <Navigate to="/" />; }; }; export default ProtectedRoute;
额外优化
- 移除无用的
render函数,直接渲染组件 - 处理API请求失败场景,避免加载器无限旋转
- 优化参数命名,提高代码可读性
内容的提问来源于stack exchange,提问作者tanaya sharma
相关产品推荐
相关产品推荐

