Refine MUI RefreshButton 完全指南:基于 useInvalidate 的页面数据刷新实现与定制
2026/9/13 3:04:49 网站建设 项目流程

Refine MUI RefreshButton 完全指南:基于 useInvalidate 的页面数据刷新实现与定制

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

导读

<RefreshButton>是 Refine 为 Material UI(MUI)集成提供的内置操作按钮之一,它的核心职责是:点击后通过useInvalidatehook 使当前页面已缓存的查询失效并重新拉取,从而把"手动刷新数据"这一操作封装成一个开箱即用、可定制、带加载态反馈的按钮组件。本文将以 RefreshButton 官方文档 为骨架,结合仓库内 MUI 按钮实现、core 层useRefreshButtonhook、useInvalidate实现 以及对应测试用例,完整讲解其用法、全部属性、底层调用链与定制方式。读完本文,你将掌握如何在 Show/Edit 等详情页、列表页甚至自定义工具栏中正确使用刷新按钮,并理解其"失效缓存 → 重新获取"的内部机制。

组件概览:一个按钮如何完成"刷新"

<RefreshButton>底层渲染的是 Material UI 的<Button>(具体到实现,实际使用的是@mui/labLoadingButton,以便支持加载态),它并不直接发起任何数据请求,而是通过useInvalidatehook 让 Refine 内部的 React Query 缓存失效,触发对应查询重新执行:

点击 RefreshButton │ ▼ useRefreshButton(core 层) │ 通过 useResourceParams 解析 resource / id ▼ useInvalidate({ resource, id, invalidates: ["detail"], ... }) │ 依据 dataProviderName 构造查询 key ▼ queryClient.invalidateQueries(queryKey) ──► 重新拉取该条记录的详情数据

这条链路中三个关键角色分别为:

  • MUI 层RefreshButton:负责渲染、图标/文本切换、加载态展示,并透传 MUIButton的全部属性;
  • core 层useRefreshButton:负责解析资源与记录 id、生成点击逻辑、加载态判定与国际化文本;
  • core 层useInvalidate:真正执行查询 key 的失效操作。

快速上手:在 Show 页面加入刷新按钮

最典型的应用场景是把刷新按钮放进Show(详情页)组件的headerButtons中。以下示例来自官方文档,完整展示了在 Refine v5 + MUI 项目中如何使用:

import { useShow } from "@refinedev/core"; import { Show, RefreshButton } from "@refinedev/mui"; import { Typography, Stack } from "@mui/material"; const PostShow: React.FC = () => { const { result: post, query } = useShow<IPost>(); const { data, isLoading } = query; return ( <Show isLoading={isLoading} headerButtons={ <RefreshButton /> } > <Typography fontWeight="bold">Id</Typography> <Typography>{post?.id}</Typography> <Typography fontWeight="bold">Title</Typography> <Typography>{post?.title}</Typography> </Show> ); }; interface IPost { id: number; title: string; }

配套的路由与资源定义如下(注意show路由中包含:id参数,这是RefreshButton能自动推断记录 id 的前提):

<RefineMuiDemo resources={[ { name: "posts", list: "/posts", show: "/posts/show/:id", }, ]} > <ReactRouter.Routes> <ReactRouter.Route path="/posts" element={ <div style={{ padding: 16 }}> <ReactRouter.Outlet /> </div> } > <ReactRouter.Route index element={<div>List page here...</div>} /> <ReactRouter.Route path="show/:id" element={<PostShow />} /> </ReactRouter.Route> </ReactRouter.Routes> </RefineMuiDemo>

运行后,详情页头部会渲染出一个带有刷新图标的 "Refresh" 按钮。点击它会触发useInvalidate,随后重新获取当前这条posts记录的数据。

从源码层面看,这里"无需传任何参数"的自动推断能力来自useRefreshButton内部调用的useResourceParams

const { identifier, id, resources } = useResourceParams({ resource: props.resource, id: props.id, });

当不显式传入resourcerecordItemId时,它们会从当前路由参数中推断出来——这正是它"零配置可用"的原因。对应类型定义中也明确标注了默认行为:

  • resource:默认从路由推断(见 ui-types 中RefineButtonResourceProps);
  • recordItemId:默认读取 URL 中的:id(见 ui-types 中RefineButtonSingleProps)。

属性详解(Properties)

recordItemId:指定要刷新哪条记录

recordItemId用于控制刷新目标记录的 id。默认情况下它会从路由参数中推断(例如/posts/show/123中的123)。在非详情页(例如列表页的自定义工具栏)中,你可以显式指定:

import { RefreshButton } from "@refinedev/mui"; const MyRefreshComponent = () => { return ( <RefreshButton resource="posts" recordItemId="123" /> ); };

点击该按钮后,useInvalidate会针对 resource 为posts、id 为123的"单条记录详情"查询发起失效并重新获取。

resource:指定要刷新的资源

resource用于控制刷新哪个资源的查询。默认同样从路由推断。下面的例子显式指定刷新categories资源下的123号记录:

import { RefreshButton } from "@refinedev/mui"; const MyRefreshComponent = () => { return ( <RefreshButton resource="categories" recordItemId="123" /> ); };

需要特别说明的是同名资源(identifier)场景:如果你在<Refine/>中配置了多个名称相同的资源,可以传入资源的identifier来代替name。此时identifier仅作为资源匹配的主键,数据提供器(data provider)的方法仍然使用<Refine/>组件中定义的资源name工作。详细说明可参考identifier相关文档。

这一点在useRefreshButton的源码中得到了印证:useResourceParams返回的是identifier(而非name),随后useInvalidateidentifier作为资源键构造查询 key,而数据请求仍走资源本身的name

const { identifier, id, resources } = useResourceParams({ ... }); // ... invalidates({ id, invalidates: ["detail"], dataProviderName: props.dataProviderName, resource: identifier, });

hideText:只显示图标

hideText用于控制是否隐藏按钮文本。设为true时,按钮只显示图标(适合放在紧凑的工具栏或表格行操作列中):

import { RefreshButton } from "@refinedev/mui"; const MyRefreshComponent = () => { return ( <RefreshButton resource="posts" recordItemId="123" hideText /> ); };

dataProviderName:多数据源时指定提供器

当项目配置了多个 data provider 时,可通过dataProviderName指定刷新动作作用于哪个数据提供器。该属性定义在 ui-types 的RefineButtonDataProps中,并贯穿useRefreshButtonuseInvalidate的整个调用链,最终用于pickDataProvider决策与查询 key 构造。

svgIconProps 与 startIcon:自定义图标

这是 MUI 集成层独有的扩展属性(定义在 packages/mui 的按钮类型)。svgIconProps用于给默认刷新图标(RefreshOutlined)传参(如颜色、尺寸),startIcon则可完全替换默认图标。实现代码中两者有明确的优先级关系:

const defaultIcon = <RefreshOutlined fontSize="small" {...svgIconProps} />; const buttonStartIcon = hideText ? undefined : startIcon ?? <RefreshOutlined {...svgIconProps} />; const buttonChildren = hideText ? startIcon ?? defaultIcon : children ?? label;

也就是说:当提供startIcon时,它优先于默认的RefreshOutlined图标;children则优先于默认的 "Refresh" 文本。

底层原理:useRefreshButton 与 useInvalidate 的实现剖析

core 层useRefreshButton:按钮逻辑的真正来源

MUI 的RefreshButton本质上是薄封装,全部业务逻辑都来自 core 层的useRefreshButton。它返回三个值:

返回值含义实现说明
onClick点击处理函数调用useInvalidate,失效目标记录的detail查询
label按钮文本通过useTranslate读取 i18n 键buttons.refresh,缺省回退为"Refresh"
loading加载态标志通过queryClient.isFetching()检测该记录详情查询是否正在请求中

其中loading的实现非常巧妙——它并非常规的"点击后置 loading",而是实时监测 React Query 的 fetching 状态:

const loading = !!queryClient.isFetching({ queryKey: keys() .data(pickDataProvider(identifier, props.dataProviderName, resources)) .resource(identifier) .action("one") .get(), });

只要目标资源的one(单条详情)查询正在请求中,按钮就自动进入加载态。这意味着即使刷新动作由其他组件触发(例如useShow初次加载),按钮也会同步展示加载中状态,并在请求结束后自动恢复。

useInvalidate:查询 key 与失效范围

点击刷新按钮最终落到useInvalidateRefreshButton传入的是invalidates: ["detail"],即只针对"单条详情"查询:

case "detail": return queryClient.invalidateQueries({ queryKey: queryKey .action("one") .id(id || "") .get(), ...invalidationFilters, // 默认 { type: "all", refetchType: "active" } ...invalidationOptions, // 默认 { cancelRefetch: false } });

关键默认值说明:

  • invalidationFilters默认{ type: "all", refetchType: "active" }:匹配该 key 下所有查询变体,且只自动重新拉取当前处于激活状态(页面可见)的查询;
  • invalidationOptions默认{ cancelRefetch: false }:不取消进行中的请求,直接触发重新拉取。

useInvalidate还支持listmanyallresourceAll等失效范围(详见 invalidate 实现),RefreshButton固定使用["detail"],这是"刷新单条记录"语义的精准映射。

测试与行为验证:测试用例如何保障按钮行为

仓库为刷新按钮提供了两层测试,可分别验证 UI 行为与核心逻辑:

1. core 层useRefreshButton逻辑测试

packages/core/src/hooks/button/refresh-button/index.spec.tsx 覆盖了:

  • 文本与 i18n:默认返回"Refresh",且可通过自定义i18nProviderbuttons.refresh键替换为其他语言文本;
  • 点击触发失效:mock 掉useInvalidate后调用onClick,断言 invalidation 确实被触发;
  • 加载态联动:先用useOne拉取一次数据,再调用onClick,断言loading先变为true、请求完成后回到false,且数据更新为最新值(Post 1Post 1 updated)——这从测试层面完整复现了"点击 → 失效 → 重新获取 → 按钮加载态"的闭环。

2. ui-tests 通用按钮测试

packages/ui-tests/src/tests/buttons/refresh.tsx 提供了跨 UI 库复用的通用断言集,MUI 侧在 index.spec.tsx 中直接绑定执行:

  • 能正常渲染、带有正确的data-testidRefineButtonTestIds.RefreshButton);
  • 支持children自定义文本;
  • hideText时不渲染文本;
  • 传入onClick时优先调用自定义处理函数,且不再触发useInvalidate(见测试中"when onClick is not passed, NOT invalidates"的断言,refresh.tsx)——这意味着重写点击行为完全由开发者掌控;
  • MUI 侧还额外测试了startIconhideText的组合:hideTexttrue且提供startIcon时只渲染自定义图标;hideTextfalse时图标进入startIcon槽位、文本作为 children。

从源码注释可以确认一个设计要点:早期版本是在 UI 包内直接调用useInvalidate,而现在统一改为使用 core 的useRefreshButton再内部调用useInvalidate,因此刷新逻辑只需在 core 中测试一次,各 UI 包只负责渲染与交互(见 refresh.tsx 中被it.skip并注释说明的旧测试)。

定制与扩展:Swizzle 与 MUI 属性透传

使用 Refine CLI Swizzle 定制组件

官方文档明确指出:可以通过 Refine CLI 对该组件执行 swizzle("拔出"组件源码到项目内)后自由修改。这是 Refine 组件体系的标准定制路径——当你需要改变按钮的结构、样式或行为,但又不想脱离 Refine 的数据流时,swizzle 是最直接的方式。

透传 MUI Button 的全部属性

RefreshButton除了自身属性外,接受 Material UIButton的全部 props(官方 API 参考中的 External Props 说明)。从实现看,MUI 按钮类型继承自RefineRefreshButtonProps(见 ui-types),其中组合了:

  • RefineButtonCommonPropshideTextchildren
  • RefineButtonResourcePropsresourceaccessControl
  • RefineButtonSinglePropsrecordItemId
  • RefineButtonDataPropsdataProviderName
  • RefineButtonLinkingPropsonClick
  • 再加上 MUI 的ButtonProps与扩展的svgIconProps(见 packages/mui 类型定义)。

因此你可以像使用普通 MUI 按钮一样传入sxsizevariantcolordisabled等属性。实现中对sx做了合并处理(sx={{ minWidth: 0, ...sx }},保证hideText图标模式下的紧凑布局),并特意从 rest props 中抽出startIcon以避免与默认图标重复渲染:

const { sx, startIcon, ...restProps } = rest;

完整属性速查表

以下为RefreshButton的核心属性汇总(依据 官方文档 API Reference 与 ui-types 类型定义):

属性类型默认值说明
resourcestring从路由推断要刷新的资源名称(或同名资源的identifier
recordItemIdBaseKey读取 URL 中:id要刷新的记录 id
dataProviderNamestring"default"多数据源时指定目标 data provider
hideTextbooleanfalsetrue时只显示图标
accessControl{ enabled?, hideIfUnauthorized? }{ enabled: true }按钮的权限控制配置
onClickPointerEventHandler默认刷新逻辑自定义点击处理(传入后不再触发默认失效)
childrenReactNode"Refresh"(i18n)自定义按钮文本
svgIconPropsSvgIconProps自定义默认刷新图标的 SVG 属性(MUI 集成扩展)
startIconReactNodeRefreshOutlined替换默认图标(透传 MUIButton
其余 MUI 属性ButtonPropssxvariantsizecolordisabled等全部透传

小结

<RefreshButton>是 Refine MUI 集成中最能体现"声明式数据流"理念的组件之一:你不需要手写"取数据 → 重新请求"的逻辑,只需要把它放进headerButtons或工具栏,它就会自动解析当前资源与记录、执行缓存失效、展示加载态,并完全兼容 MUI 的样式体系。理解它的实现(core 层useRefreshButton+useInvalidate的查询 key 机制)后,你也能举一反三——同样的失效机制也被 Edit、Delete 等按钮用于操作完成后的数据同步。若需要更深入理解失效范围(list/many/all)的用法,可继续阅读 useInvalidate 文档。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询