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

React应用用户权限实现遇重复渲染问题求助

React应用用户权限控制问题:重复渲染与权限校验实现

我正在React应用中实现用户权限控制,但目前应用持续触发数百次重复渲染,即使尝试在RequireAuth组件中直接计算权限(注释代码所示)也未能解决问题。核心需求是:仅当用户权限包含allowedRoles指定的权限时,才显示Outlet组件。


UserContext 代码

import axios from "axios";
import React, { createContext, useEffect, useState } from "react";


export const UserContext = createContext(null)

export const UserProvider = (props) => {
    const [token, setToken] = useState(localStorage.getItem("accessToken"));
    const [currentUser, setCurrentUser] = useState({});
    const [permissions, setPermissions] = useState([]);

    useEffect(() => {
        const getCurrentUser = () => {
            const headers = {
                headers: { Authorization: "Bearer " + token, }

            }

            axios.get("/api/users/me", headers)
                .then((res) => {
                    setCurrentUser(res.data);
                })
                .catch(() => {
                    setToken(null);
                    setCurrentUser({});
                })
                .finally(localStorage.setItem("accessToken", token))



        };
        getCurrentUser();
        const getPermissions = () => {
            if (currentUser.user_roles) {
                let perm = []
                for (let i = 0; i < currentUser.user_roles.length; i += 1) {
                    if (currentUser.user_roles[i].roles.role_permissions) {
                        for (let j = 0; j < currentUser.user_roles[i].roles.role_permissions.length; j += 1) {
                            perm.push(currentUser.user_roles[i].roles.role_permissions[j].permissions.name)
                        }
                    }
                }
                setPermissions(perm)
            }
        }
        getPermissions();
        console.log(permissions)
    }, [token]);



    return (
        <UserContext.Provider value={[[token, setToken], [currentUser, setCurrentUser], [permissions, setPermissions]]}>
            {props.children}
        </UserContext.Provider>
    )
}

useAuth 钩子代码

import { useContext } from "react";
import { UserContext } from "../contex/UserContex";

const useAuth = () => {
    return useContext(UserContext);
}
export default useAuth;

RequireAuth 组件代码

import { useEffect, useState } from "react";
import { useLocation, Navigate, Outlet } from "react-router-dom";
import useAuth from "../hooks/useAuth";

const RequireAuth = ({ allowedRoles }) => {
    const [[], [currentUser], [permissions]] = useAuth();
    const location = useLocation();

    /** 
        useEffect(() => {
            if (currentUser.user_roles) {
                let perm = []
                for (let i = 0; i < currentUser.user_roles.length; i += 1) {
                    if (currentUser.user_roles[i].roles.role_permissions) {
                        for (let j = 0; j < currentUser.user_roles[i].roles.role_permissions.length; j += 1) {
                            perm.push(currentUser.user_roles[i].roles.role_permissions[j].permissions.name)
                        }
                    }
                }
                setPermissions(perm)
            }
        }, [])
    
    */
    /** currentUser?.user_roles?.map((userRoles) => userRoles.roles?.role_permissions?.find(perm => allowedRoles?.includes(perm.permissions.name))) */

    return (

        allowedRoles.find((role) => permissions.includes(role))
            ? <Outlet />
            : currentUser?.username
                ? <Navigate to="/" state={{ from: location }} replace />
                : <Navigate to="/login" state={{ from: location }} replace />
    )
}

export default RequireAuth

用户对象结构

{
  "username": "admin",
  "first_name": "admin",
  "last_name": "admin",
  "user_id": 1,
  "user_roles": [
    {
      "user_role_id": 1,
      "user_id": 1,
      "role_id": 1,
      "roles": {
        "name": "Admin",
        "role_id": 1,
        "role_permissions": [
          {
            "role_permission_id": 1,
            "role_id": 1,
            "permission_id": 1,
            "permissions": {
              "name": "admin:all",
              "permission_id": 1,
              "description": "Admin Privilages",
              "tag": "All"
            }
          }
        ]
      }
    }
  ]
}

问题分析与修复方案

重复渲染原因

  1. Context值引用不稳定:Provider的value使用嵌套数组,每次组件渲染都会生成新的数组引用,导致所有消费Context的组件强制重渲染。
  2. useEffect依赖缺失:权限计算逻辑依赖currentUser,但useEffect仅监听token变化,导致currentUser更新后无法重新计算权限,同时错误的触发时机导致无效状态更新。
  3. 异步逻辑顺序错误:getPermissions在getCurrentUser的异步请求完成前执行,此时currentUser仍是初始空对象,权限计算无效。

修复后的代码

修改后的 UserContext

import axios from "axios";
import React, { createContext, useEffect, useState } from "react";

export const UserContext = createContext(null);

export const UserProvider = (props) => {
    const [token, setToken] = useState(localStorage.getItem("accessToken"));
    const [currentUser, setCurrentUser] = useState({});
    const [permissions, setPermissions] = useState([]);

    // 单独处理用户信息获取
    useEffect(() => {
        const getCurrentUser = async () => {
            if (!token) {
                setCurrentUser({});
                return;
            }
            try {
                const headers = { Authorization: `Bearer ${token}` };
                const res = await axios.get("/api/users/me", { headers });
                setCurrentUser(res.data);
                localStorage.setItem("accessToken", token);
            } catch {
                setToken(null);
                setCurrentUser({});
                localStorage.removeItem("accessToken");
            }
        };
        getCurrentUser();
    }, [token]);

    // 单独处理权限提取,依赖currentUser变化
    useEffect(() => {
        if (!currentUser.user_roles) {
            setPermissions([]);
            return;
        }
        // 用flatMap简化嵌套循环
        const permList = currentUser.user_roles.flatMap(userRole => 
            userRole.roles?.role_permissions?.map(rolePerm => rolePerm.permissions.name) || []
        );
        setPermissions(permList);
    }, [currentUser]);

    // 使用对象作为Context值,保持引用稳定
    return (
        <UserContext.Provider 
            value={{ token, setToken, currentUser, setCurrentUser, permissions }}
        >
            {props.children}
        </UserContext.Provider>
    );
};

修改后的 useAuth 钩子

import { useContext } from "react";
import { UserContext } from "../contex/UserContex";

const useAuth = () => {
    return useContext(UserContext);
};

export default useAuth;

修改后的 RequireAuth 组件

import { useLocation, Navigate, Outlet } from "react-router-dom";
import useAuth from "../hooks/useAuth";

const RequireAuth = ({ allowedRoles }) => {
    const { currentUser, permissions } = useAuth();
    const location = useLocation();

    // 用some替代find,语义更明确:检查是否存在匹配权限
    const hasValidPermission = allowedRoles.some(role => permissions.includes(role));

    return hasValidPermission ? (
        <Outlet />
    ) : currentUser?.username ? (
        <Navigate to="/" state={{ from: location }} replace />
    ) : (
        <Navigate to="/login" state={{ from: location }} replace />
    );
};

export default RequireAuth;

关键修改点说明

  1. Context值改为对象:避免每次渲染生成新引用,仅当内部状态变化时才触发消费组件更新。
  2. 拆分useEffect:将用户信息获取和权限提取拆分为两个独立的useEffect,分别监听token和currentUser,确保逻辑触发时机正确。
  3. 简化异步代码:使用async/await替代Promise链式调用,逻辑更清晰。
  4. 优化权限计算:用flatMap和map简化嵌套循环,代码更简洁易读。
  5. 语义化权限校验:用some替代find,明确表达"是否存在任一匹配权限"的逻辑。

内容的提问来源于stack exchange,提问作者Loson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 20:57:13