You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

问题根源与修复方案

核心问题

  1. React无法感知服务内部状态变化:ConfigService是固定实例,内部_permissions更新时,React不会触发组件重渲染,导致useEffect仅执行一次,此时权限可能还未加载完成。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:27:52