
refine useCan Hook 深度指南基于 Access Control Provider 的权限校验、查询缓存与源码解析【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refineuseCan是 refine 核心包中面向 Access Control Provider 的权限校验 Hook。它以accessControlProvider.can作为 TanStack Queryreact-queryuseQuery的查询函数将“权限是否允许”这一判定纳入统一的数据查询体系从而获得缓存、去重与状态管理能力。读完本文你将掌握useCan的完整参数、返回值与性能优化手段并通过仓库源码理解其底层实现与边界行为能在 refine 3.x 项目中独立实现细粒度的 RBAC / ABAC 权限控制。本文主体对应仓库文档 useCan.md所有源码依据均来自当前仓库packages/core与documentation/versioned_docs目录。useCan 是什么把「权限判定」变成一次数据查询在 refine 的权限模型中accessControlProvider只需实现一个异步方法can用于回答“用户对某资源执行某动作是否被允许”。refine 刻意保持 API 无关性agnostic以便对接 RBAC、ABAC、ACL 等不同方案以及 Casbin、CASL、Cerbos 等库——can方法正是这些方案的统一入口详见 accessControl-provider.md。useCan的核心设计是把can函数当作useQuery的查询函数。它接受can所需的一切参数resource、action、params并额外支持queryOptions用于配置useQuery最终返回useQuery的查询结果。从源码看其签名与实现位于 packages/core/src/hooks/accessControl/useCan/index.tsexport const useCan ({ action, resource, params, queryOptions: hookQueryOptions, }: UseCanProps): UseQueryResultCanReturnType { // 从 AccessControlContext 中取出 can 与全局 options const { can, options: globalOptions } useContext(AccessControlContext); ... };它从AccessControlContext读取can函数再经由useQuery执行因此useCan天然具备 react-query 的全部能力查询状态机loading / error / success、缓存、重试控制、并发去重等。版本说明本仓库 version-3.xx.xx 文档中的示例使用pankod/refine-core包名refine 3.x 时代的命名当前仓库较新版本中对应包为refinedev/core用法保持一致按你所使用的版本选择导入来源即可。前置准备Access Control Provider 与 can 方法useCan的输入输出严格对齐can的类型。在 packages/core/src/contexts/accessControl/types.ts 中可以确认三组核心类型export type CanResponse { can: boolean; reason?: string; [key: string]: unknown; }; export type CanParams { resource?: string; // 资源名用于 API 数据交互 action: string; // 对资源的意图动作 params?: { resource?: IResourceItem { children?: ITreeResource[] }; id?: BaseKey; [key: string]: any; }; }; export type CanReturnType { can: boolean; reason?: string; }; export type CanFunction ({ resource, action, params, }: CanParams) PromiseCanReturnType;同时IAccessControlContext除了can之外还支持全局options见 types.tstype AccessControlOptions { buttons?: { enableAccessControl?: boolean; hideIfUnauthorized?: boolean; }; queryOptions?: MakeOptional UseQueryOptionsCanReturnType, queryFn | queryKey ; }; export interface IAccessControlContext { can?: CanFunction; options?: AccessControlOptions; }这意味着你既可以在单次调用时传queryOptions也可以在 Provider 层配置全局的queryOptions后文“源码深挖”会说明二者如何合并。基本用法useCan的最基本用法如下沿用原文档示例import { useCan } from pankod/refine-core; const { data } useCan({ resource: resource-you-ask-for-access, action: action-type-on-resource, params: { foo: optional-params }, });调用后data即为can方法的返回结果{ can: boolean; reason?: string }。例如判断“当前用户能否创建 post”结合 Provider 定义与 Hook 调用的完整示例为Refine accessControlProvider{{ can: async ({ resource, action }) { if (resource post action create) { return Promise.resolve({ can: false, reason: Unauthorized, }); } return Promise.resolve({ can: true }); }, }} // ... /; // inside your component const { data: canCreatePost } useCan({ action: create, resource: post, }); console.log(canCreatePost); // { can: false, reason: Unauthorized }reason字段会随can一起返回可用于向用户展示被拒原因在 refine 内置按钮中reason还会显示在按钮禁用态的工具提示tooltip中参见 accessControl-provider.md。Properties 详解useCan接受四个属性前三个原样透传给can最后一个用于配置底层查询。resource必填传递给can函数的resource参数表示要请求访问的资源名useCan({ resource: resource-you-ask-for-access, });action必填传递给can函数的action参数表示在资源上执行的意图动作如list、create、edit、show、delete等useCan({ action: resource-you-ask-for-access, });原文档此处的示例字符串沿用了占位写法实际使用时应填写动作类型而非资源名例如action: edit。params传递给can函数的params参数通常用于携带记录级信息例如id或完整的资源对象以实现 ABACuseCan({ params: { foo: optional-params }, });queryOptions透传给 TanStack QueryuseQuery的查询配置典型用途是调整staleTime、cacheTime、enabled、queryKey、queryFn等useCan({ queryOptions: { staleTime: 5 * 60 * 1000, // 5 minutes }, });在 useCan/index.ts 中UseCanProps定义为CanParams与queryOptions的交集其中queryOptions允许自定义queryKey和queryFnexport type UseCanProps CanParams { queryOptions?: OmitUseQueryOptionsCanReturnType, queryKey { queryKey?: UseQueryOptionsCanReturnType[queryKey]; }; };返回值useCan的返回值就是useQuery的查询结果类型为QueryObserverResult数据部分为CanReturnType。你可以直接使用 react-query 提供的全部字段如data、isLoading、isError、isFetched、refetch等。原文档给出的 API 对照如下属性描述CanReturnType查询结果数据类型即{ can: boolean; reason?: string }原文档同时提及HttpError类型的错误场景实际以你使用的 refine 版本接口定义为准描述类型TanStack QueryuseQuery的查询结果QueryObserverResult{ data: CanReturnType; }源码中为UseQueryResultCanReturnType值得注意的一个兜底行为在 useCan/index.ts 中如果当前上下文中不存在can函数例如未配置accessControlProvideruseCan不会抛错而是直接返回{ data: { can: true } }即默认放行return typeof can undefined ? ({ data: { can: true } } as typeof queryResponse) : queryResponse;性能优化善用 staleTime 与 cacheTime随着应用内权限检查点的增加尤其当权限判定依赖远程端点时性能可能明显下降。由于 refine 基于 TanStack Query缓存权限检查结果能带来巨大收益最简单的方式就是配置staleTime与cacheTimeimport { useCan } from pankod/refine-core; // inside your component const { data } useCan({ resource: resource-you-ask-for-access, action: action-type-on-resource, params: { foo: optional-params } }, queryOptions: { staleTime: 5 * 60 * 1000, // 5 minutes } });staleTime数据在多少毫秒内被视为“新鲜”期间命中缓存不会重新请求cacheTime不再被使用的查询结果在缓存中保留的时长。关于默认值accessControl-provider.md 明确说明refine 自带的权限检查点默认使用 5 分钟cacheTime、0staleTime。这意味着默认情况下每次挂载都可能重新请求因为staleTime: 0而缓存 5 分钟。如果你的权限变更不频繁调大staleTime可以有效减少重复请求。源码深挖useCan 的实现细节1. queryKey 的自动生成在 useCan/index.ts 中查询键由useKeys()生成将resource、action、params含enabled状态全部纳入键结构const queryResponse useQueryCanReturnType({ queryKey: keys() .access() .resource(resource) .action(action) .params({ params: { ...paramsRest, resource: sanitizedResource }, enabled: mergedQueryOptions?.enabled, }) .get(), queryFn: () can?.({ action, resource, params: { ...paramsRest, resource: sanitizedResource }, }) ?? Promise.resolve({ can: true }), enabled: typeof can ! undefined, ...mergedQueryOptions, meta: { ...mergedQueryOptions?.meta, ...getXRay(useCan, resource, [ useButtonCanAccess, useNavigationButton, ]), }, retry: false, });几个关键点enabled: typeof can ! undefined未配置权限 Provider 时查询不会执行配合上面的兜底返回{ can: true }retry: false权限请求默认不自动重试与一般数据请求的默认策略不同避免权限接口失败时产生无意义的重复请求meta注入getXRay信息为 refine devtools 提供 Hook 调用链路追踪useButtonCanAccess、useNavigationButton等queryFn兜底can为undefined时返回Promise.resolve({ can: true })保证类型安全与行为一致。2. 全局 queryOptions 与局部 queryOptions 合并useCan支持在 Provider 层配置全局queryOptions见AccessControlOptions.queryOptions。源码中的合并逻辑是浅合并全局配置作为基础单次调用传入的配置覆盖之const { queryOptions: globalQueryOptions } globalOptions || {}; const mergedQueryOptions { ...globalQueryOptions, ...hookQueryOptions, };在 index.spec.tsx 中有对应测试当 Provider 配置options: { queryOptions: { enabled: false } }时can不会被调用验证了全局配置确实生效。3. sanitizeResource防止不可序列化的 icon 破坏 queryKey由于 react-query 会对 queryKey 做字符串化处理如果params.resource中带有icon等包含ReactNode的属性可能引发循环依赖序列化错误。为此useCan在构建 queryKey 与调用can之前会通过sanitizeResource清洗资源对象见 sanitize-resource/index.tsexport const sanitizeResource (resource) { const { list, edit, create, show, clone, children, meta, icon, ...restResource } resource; const { icon: _metaIcon, ...restMeta } meta ?? {}; return { ...restResource, ...(meta ? { meta: restMeta } : {}), }; };该函数会剔除资源对象中不可序列化的list / edit / create / show / clone组件引用、icon含meta.icon等属性只保留纯数据字段。相关行为在 index.spec.tsx 有专门测试传入带meta.icon的资源对象后can实际收到的params.resource中icon已被移除。4. useCanWithoutCache跳过缓存的直通变体除useCan外refine 还提供了useCanWithoutCacheuseCanWithoutCache.ts。它不做任何查询缓存直接从AccessControlContext取出can函数并返回同时应用相同的sanitizeResource清洗逻辑export const useCanWithoutCache (): IAccessControlContext { const { can: canFromContext } React.useContext(AccessControlContext); // 包装一层对 params.resource 做 sanitize 后调用原始 can ... return { can }; };适用场景当某次权限判定必须“实时”执行、不能命中缓存时例如刚完成角色变更需要立即刷新判定结果使用该变体可获得不带缓存的can函数引用。5. 组件与框架层的联动useCan是 refine 权限体系的底层 Hook上层还有多个消费方CanAccess /组件内部直接调用useCan见 canAccess/index.tsx根据data.can决定渲染children还是fallback并支持onUnauthorized回调路由层pankod/refine-nextjs-router、pankod/refine-react-router、pankod/refine-react-location会对 CRUD 页面[resource]/[action]做权限检查失败时展示catchAll或标准错误页Sider 菜单不可访问的资源不会出现在侧边栏各类按钮List / Create / Clone / Edit / Delete / Show权限判定失败时按钮被禁用并展示reason。各检查点的具体{ resource, action, params }参数约定可参阅 accessControl-provider.md 的 “List of Default Access Control Points” 章节。测试用例验证仓库为useCan提供了完整的单元测试见 packages/core/src/hooks/accessControl/useCan/index.spec.tsx覆盖了以下行为可作为你理解和使用该 Hook 的参考清单测试场景验证结论can返回{ can: true, reason: Access granted }正确透传data.can与data.reasoncan返回{ can: false, reason: Access Denied }拒绝场景数据透传正确Provider 未提供canundefined返回兜底{ data: { can: true } }不抛错params.resource携带meta.iconsanitizeResource生效can收到清洗后的资源对象queryOptions.enabled: falsecan不会被调用查询被禁用自定义queryOptions.queryKey覆盖默认 queryKey缓存按自定义键存储自定义queryOptions.queryFn覆盖默认查询函数can不再被调用Provider 全局options.queryOptions全局配置生效可统一禁用查询实践建议优先使用useCan做组件内权限判断它能复用 react-query 缓存多个组件检查相同resource action时只会发起一次请求合理设置staleTime权限策略变化不频繁的应用可设置较大的staleTime如 5 分钟显著降低远程权限接口压力需要即时响应的场景则保持默认或设为 0对 ABAC 场景善用params.resourcecan方法可通过params.resource拿到完整资源对象含你在Refine /中定义的meta等字段实现基于属性的授权注意 queryKey 的序列化限制不要在params.resource中携带icon等不可序列化内容useCan会自动清洗但了解这一点有助于排查异常 queryKey实时判定用useCanWithoutCache需要绕过缓存、强制走最新权限逻辑时使用。更多权限控制实战可参考仓库中的 examples/access-control-casbin 示例目录。至此你已经掌握了useCan从参数、返回值到性能调优与源码原理的完整链路可以在自己的 refine 项目中放心使用它构建细粒度、可缓存的权限控制。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考