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" } } ] } } ] }
问题分析与修复方案
重复渲染原因
- Context值引用不稳定:Provider的value使用嵌套数组,每次组件渲染都会生成新的数组引用,导致所有消费Context的组件强制重渲染。
- useEffect依赖缺失:权限计算逻辑依赖
currentUser,但useEffect仅监听token变化,导致currentUser更新后无法重新计算权限,同时错误的触发时机导致无效状态更新。 - 异步逻辑顺序错误:
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;
关键修改点说明
- Context值改为对象:避免每次渲染生成新引用,仅当内部状态变化时才触发消费组件更新。
- 拆分useEffect:将用户信息获取和权限提取拆分为两个独立的useEffect,分别监听
token和currentUser,确保逻辑触发时机正确。 - 简化异步代码:使用
async/await替代Promise链式调用,逻辑更清晰。 - 优化权限计算:用
flatMap和map简化嵌套循环,代码更简洁易读。 - 语义化权限校验:用
some替代find,明确表达"是否存在任一匹配权限"的逻辑。
内容的提问来源于stack exchange,提问作者Loson
相关产品推荐
相关产品推荐

