You need to enable JavaScript to run this app. 如何在React.js中实现基于角色的权限控制并封装按需查询共享服务
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.22 11:06:03