如何在React.js中实现基于角色的权限控制并封装按需查询共享服务
React 基于角色的权限控制落地方案
一、权限枚举的定义与存放规范
- 存放位置:统一放置在项目全局类型目录
src/types/permissions.ts,与业务组件、接口服务层完全解耦。所有代码中涉及的权限标识,必须从该文件导入,禁止在业务逻辑里硬编码权限字符串,避免后续权限值调整时出现全局漏改的问题。 - 定义规范:用TypeScript的强类型约束枚举结构,保证枚举值与后端接口的
permissionType字段完全对齐,从类型层面拦截传错权限值的问题。
// src/types/permissions.ts // 与后端约定的权限标识枚举,覆盖全部20-25项独立权限 export const Permission = { ViewUser: 'viewUser', EditUser: 'editUser', ViewReport: 'viewReport', ModifyReport: 'modifyReport', // 补充剩余权限项,字段值与后端保持1:1对应 } as const; export type PermissionType = typeof Permission[keyof typeof Permission]; // 与后端接口返回结构对齐的权限判定结果类型 export interface PermissionResult { canView: boolean; canEdit: boolean; }
二、统一权限查询共享服务封装
- 存放位置:与项目其他业务API服务平级,放在
src/services/permission.ts,直接复用项目已封装的统一请求实例(比如全局配置了拦截器的axios客户端),无需单独搭建请求逻辑。 - 核心能力设计:
- 内存结果缓存:已经查询过的权限判定结果存在内存Map中,用户登录会话有效期内直接命中缓存返回,不重复发起请求。
- 同权限请求去重:同个权限的多个并发请求,复用同一个Promise实例,避免多个组件同时挂载时对同一个权限发起重复HTTP请求,减少服务端压力。
- 异常兜底逻辑:权限请求失败时,默认返回无权限的判定结果,杜绝越权展示的风险。
- 预留缓存清除方法:在用户退出登录、临时切换角色的场景下,可调用方法清空内存缓存,重新拉取最新权限。
// src/services/permission.ts import request from './request'; import { PermissionType, PermissionResult } from '@/types/permissions'; // 内存缓存:key为权限标识,value为对应的权限判定结果 const permissionCache = new Map<PermissionType, PermissionResult>(); // 进行中的请求队列:用于同权限请求去重 const pendingRequests = new Map<PermissionType, Promise<PermissionResult>>(); // 清除全部缓存,用于退出登录、角色变更场景 export function clearPermissionCache() { permissionCache.clear(); pendingRequests.clear(); } export async function getPermission(permission: PermissionType): Promise<PermissionResult> { // 命中缓存直接返回结果 if (permissionCache.has(permission)) { return permissionCache.get(permission)!; } // 同权限已有正在进行的请求,直接复用Promise,不重复发请求 if (pendingRequests.has(permission)) { return pendingRequests.get(permission)!; } // 发起新的权限查询,前端无需传递用户身份字段 const requestPromise = request<PermissionResult>({ url: '/api/check-permission', method: 'GET', params: { permissionType: permission } }).then(res => { // 请求成功后写入缓存,清理pending记录 permissionCache.set(permission, res); pendingRequests.delete(permission); return res; }).catch(err => { // 请求失败时清理pending记录,允许后续重试 pendingRequests.delete(permission); throw err; }); pendingRequests.set(permission, requestPromise); return requestPromise; }
三、组件端的按需复用实现
- 封装通用自定义Hook:放在
src/hooks/usePermission.ts,组件只需要传入所需的权限标识,Hook会自动在组件挂载时触发对应权限的查询,返回加载状态、权限判定结果,不需要每个组件自行编写请求逻辑。 - 完全按需触发请求:组件加载时仅发起自身引用的权限对应的API请求,没有被组件引用的权限完全不触发请求,不需要一次性拉取全量权限列表,减少首屏加载的网络开销。
// src/hooks/usePermission.ts import { useEffect, useState } from 'react'; import { getPermission } from '@/services/permission'; import { PermissionType, PermissionResult } from '@/types/permissions'; export function usePermission(permission: PermissionType) { const [permissionData, setPermissionData] = useState<PermissionResult | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { let isMounted = true; setLoading(true); getPermission(permission) .then(res => { if (isMounted) setPermissionData(res); }) .catch(() => { // 权限查询失败兜底为无权限 if (isMounted) setPermissionData({ canView: false, canEdit: false }); }) .finally(() => { if (isMounted) setLoading(false); }); // 组件卸载时标记状态,避免内存泄漏 return () => { isMounted = false; }; }, [permission]); return { permissionData, loading, canView: permissionData?.canView ?? false, canEdit: permissionData?.canEdit ?? false }; }
组件实际使用示例:
import { Permission } from '@/types/permissions'; import { usePermission } from '@/hooks/usePermission'; function UserManagePage() { // 组件挂载时自动发起对应权限的查询,无需手动写请求逻辑 const { canView, canEdit, loading } = usePermission(Permission.ViewUser); if (loading) return <div>权限校验中...</div>; if (!canView) return <div>无当前页面访问权限</div>; return ( <div> <h1>用户管理</h1> {canEdit && <button>编辑用户</button>} {/* 页面其余业务逻辑 */} </div> ); }
内容的提问来源于stack exchange,提问作者Devang Patel
相关产品推荐
相关产品推荐

