
Refine v5 数据 Hook useUpdate 实战更新记录的参数体系、乐观更新与缓存失效机制【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine在 Refine 内部工具开发中编辑页、表单提交后的记录修改都依赖同一个数据层能力useUpdateHook。它基于 TanStack Query 的useMutation扩展而来把dataProvider的update方法封装为标准的 mutation 函数并叠加了乐观更新、可撤销undoable延迟提交、查询缓存失效、实时发布与审计日志等框架级行为。读完后你将掌握useUpdate的完整参数体系resource/id/values/mutationMode/invalidates/optimisticUpdateMap等、mutate与mutateAsync的用法差异以及它在onMutate/onSuccess/onSettled各阶段对查询缓存、Live Provider、Audit Log Provider 的具体处理逻辑。基本用法与底层调用链useUpdate用于更新一条记录。它使用传给Refine组件的 dataProvider 的update方法作为 mutation 函数。返回对象中包含mutate触发 mutation和mutation完整的 TanStack QueryuseMutation返回值import { useUpdate } from refinedev/core; const { mutate, mutation } useUpdate({ resource: products, }); mutate({ id: 1, values: { name: New Product, material: Wood, }, }); // 通过 mutation 对象访问 mutation 状态 console.log(mutation.isPending); // mutation loading 状态 console.log(mutation.data); // mutation 响应数据 console.log(mutation.error); // mutation 错误从源码实现看packages/core/src/hooks/data/useUpdate.tsmutationFn内部会先做三项参数校验id未定义、values未提供、resource未匹配都会抛出带前缀的错误如[useUpdate]: id is not defined but is required in edit and clone actions然后通过select(resourceName)解析出资源的name与identifier经pickDataProvider选定具体的 data provider 后调用dataProvider(identifier, dataProviderName).updateTData, TVariables({ resource: resource.name, id, variables: values, meta: combinedMeta, });这里有两个值得注意的映射细节values最终以variables字段名传给 dataProvider 的update方法meta会先经过getMeta与全局配置合并combinedMeta再一并传入。另外Hook 使用keys().data().mutation(update)作为统一的mutationKey即所有 update mutation 共享同一 mutation key这使 Refine Devtools 等外部工具能够归类追踪 update 请求。Mutation ParametersHook 参数与 mutate 参数的优先级Mutation 参数可以传给mutate函数也可以作为 props 传给useUpdate。传给mutate的参数会覆盖 Hook 上的参数。可以把 Hook 参数理解为默认值mutate参数理解为每次 mutation 的具体/动态值const { mutate, mutation } useUpdate({ /* parameters */ }); mutate({ /* 覆盖传给 useUpdate 的参数 */ }); if (mutation.isPending) { // 处理 loading 状态 } if (mutation.isError) { console.error(mutation.error); }标注为必填的参数既可以通过useUpdateprops 提供也可以通过mutate参数提供两者至少满足其一。从源码看这一props 兜底 mutate 覆盖的策略通过解构默认值实现mutationFn内使用id idFromProps、values valuesFromProps、resource: resourceName resourceFromProps等默认参数mutate传值时自然覆盖。此外 Hook 返回的mutate/mutateAsync均被 包装为 variables 可选的函数——当所有参数都写在 Hook props 上时可以直接调用mutate()而不传任何参数。resource必填会作为参数传给 dataProvider 的update方法。通常用作 API 端点路径但最终取决于你在update方法中如何处理resourceconst { mutate, mutation } useUpdate(); mutate({ resource: categories, });如果存在多个同名资源可以传identifier而不是资源的name。它仅作为资源的主匹配键使用dataProvider 方法本身仍会使用Refine/组件中定义的资源name。可参考Refine/组件的identifier文档。id必填传给update方法用于确定要更新哪条记录mutate({ id: 123, });values必填传给update方法通常是发送到服务器用于更新的数据mutate({ values: { name: New Category, description: New Category Description, }, });mutationModepessimistic、optimistic 与 undoablemutationMode决定 mutation 以哪种模式执行共有三种模式pessimistic默认、optimistic、undoable每种模式对应不同的用户体验。详细的模式设计参见 mutation mode 教程mutate({ mutationMode: undoable, });Hook 参数上未指定时会回退到Refine全局配置中的mutationMode与undoableTimeout。这一回退逻辑在 useMutationMode 中实现preferredMutationMode ?? mutationMode上下文值兜底。undoableTimeout当mutationMode为undoable时undoableTimeout决定真正执行 mutation 前等待的时长默认5000毫秒mutate({ mutationMode: undoable, undoableTimeout: 10000, });源码层面undoable 模式的实现方式是mutationFn返回一个延迟执行的 Promise——真正的 API 调用被包进doMutation函数先通过notificationDispatch({ type: ActionTypes.ADD, ... })把{ doMutation, cancelMutation, seconds: undoableTimeout, ... }加入撤销通知队列由通知组件倒计时结束后再触发doMutation。取消时则reject({ message: mutationCancelled })且onError中对mutationCancelled错误会跳过错误通知避免弹出误导性报错useUpdate.ts。onCancelonCancel仅在mutationMode为undoable时可用它会接收一个cancelMutation函数用于取消进行中的 mutationimport { useRef } from react; import { useUpdate } from refinedev/core; const MyComponent () { const { mutate, mutation } useUpdate(); const cancelRef useRef(() void) | null(null); const updateItem () { mutate({ //... mutationMode: undoable, onCancel: (cancelMutation) { cancelRef.current cancelMutation; }, }); }; const cancelUpdate () { cancelRef.current?.(); }; return ( button onClick{updateItem}Update/button button onClick{cancelUpdate}Cancel/button / ); };定义了onCancel后框架不会自动显示撤销通知undoable notification。源码中这对应入队 payload 里的isSilent: !!onCancel字段让你可以完全自定义取消流程——比如展示自定义通知或让用户以其他方式取消 mutation。查询失效mutation 完成后缓存如何刷新useUpdatemutation 成功执行后默认会失效当前resource下的list、many、detail三类查询。也就是说如果同一页面使用了useList、useMany或useOnemutation 完成后它们会重新拉取数据。该行为可以通过invalidates属性修改。从 onSettled 实现 看无论成功还是失败框架都会调用invalidateStore并把invalidates invalidatesFromProps ?? [list, many, detail]传入同时通过notificationDispatch({ type: ActionTypes.REMOVE })清理 undoable 通知随后再触发用户通过mutationOptions传入的自定义onSettled回调。实时发布Live Provider该能力仅在配置了 Live Provider 时可用。mutation 成功时useUpdate会调用liveProvider的publish方法把变更广播给客户端订阅者便于其他客户端同步更新。onSuccess 中的实际调用 固定了事件结构publish?.({ channel: resources/${resource.name}, type: updated, payload: { ids: data.data?.id ? [data.data.id] : undefined, }, date: new Date(), meta: { ...combinedMeta, dataProviderName }, });即频道为resources/{资源名}、类型固定为updated、payload 中携带受影响记录 id 数组meta 里附带合并后的 meta 与dataProviderName。审计日志Audit Log Provider该能力仅在配置了 Audit Log Provider 时可用。mutation 成功后useUpdate会调用auditLogProvider的log方法源码中为log.mutate把变更写入数据库。从 源码 可以看到它记录的内容action: update、resource、data即本次values、previousData从one查询缓存中按values的字段名逐字段提取更新前的旧值、以及剥离掉fields/operation/variables后的 meta附加dataProviderName与id。通知定制successNotification 与 errorNotification以下两个 prop 需要 NotificationProvider 才能生效。successNotification用于定制数据获取成功时useUpdate调用NotificationProvider的open所展示的成功通知mutate({ successNotification: (data, values, resource) { return { message: ${data.title} Successfully fetched., description: Success with no errors, type: success, }; }, });errorNotification用于定制请求失败时的错误通知mutate({ errorNotification: (data, values, resource) { return { message: Something went wrong when getting ${data.id}, description: Error, type: error, }; }, });从源码看两个通知最终都通过handleNotification发送并支持 i18nsuccessNotification为函数时以函数返回值为准否则使用翻译键notifications.editSuccess兜底文案为Successfully updated ${resourceSingular}errorNotification同理默认文案为Error when updating ${resourceSingular} (status code: ${statusCode})其中description取自err.message。meta向 dataProvider 传递附加上下文meta是一个特殊属性用于向 dataProvider 方法传递额外信息典型用途针对特定用例定制 dataProvider 方法行为使用普通 JS 对象JSON生成 GraphQL 查询。以下示例在meta中传headersupdate方法读取它来附加请求头const { mutate, mutation } useUpdate(); mutate({ meta: { headers: { x-meta-data: true }, }, }); const myDataProvider { //... update: async ({ resource, id, variables, meta, }) { const headers meta?.headers ?? {}; const url ${apiUrl}/${resource}/${id}; //... const { data } await httpClient.patch(url, variables, { headers }); return { data, }; }, //... };可参考 General Concepts 文档的 meta 章节 了解更多。一个细节是在onMutate的乐观缓存更新中meta里的gqlMutation/gqlQuery会被剔除preferredMeta作为 list 查询 key 的 params 参与匹配这保证了 GraphQL 场景下缓存 key 的正确性。dataProviderName 与 invalidatesdataProviderName当存在多个 dataProvider 时用它指定本次 mutation 使用哪一个mutate({ dataProviderName: second-data-provider, });源码中所有涉及查询 key 构造与失效的位置都会经过pickDataProvider(identifier, dataProviderName, resources)选定 provider因此dataProviderName会同时影响 mutation 调用、缓存读写与失效范围。invalidates指定 mutation 完成后要失效哪些查询。默认失效当前resource的list、many和detail即useList/useMany/useOne在 mutation 完成后重新拉取数据mutate({ invalidates: [list, many, detail], });overtimeOptions请求超时提示如果希望为请求提供加载超时体验例如请求耗时过长时展示提示可以传overtimeOptions。interval为毫秒级时间间隔onInterval在每个间隔被调用Hook 返回overtime对象其中elapsedTime为已流逝的毫秒数请求完成后变为undefinedconst { overtime, mutation } useUpdate({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // 用法示例 { overtime.elapsedTime 4000 ( divthis takes a bit longer than expected/div ); }实现上该能力由 useLoadingOvertime 驱动以mutationResult.isPending作为计时开关。optimisticUpdateMap定制乐观缓存更新当 mutation 模式为optimistic或undoable时useUpdate会在等待服务器响应之前自动更新查询缓存。如需关闭或定制这一行为可以传optimisticUpdateMap。list、many、detail是该对象的键传true表示自动更新对应缓存传false表示不更新mutate({ //... mutationMode: optimistic, optimisticUpdateMap: { list: true, many: true, detail: false, }, });在上述配置中list与many查询会收到自动缓存更新而detail查询缓存不受影响。也可以为这三个键提供函数来自定义缓存更新逻辑。函数会接收previous数据、values与id你负责返回更新后的数据mutate({ //... mutationMode: optimistic, optimisticUpdateMap: { list: (previous, values, id) { if (!previous) { return null; } const data previous.data.map((record) { if (record.id id) { return { foo: bar, ...record, ...values, }; } return record; }); return { ...previous, data, }; }, many: (previous, values, id) { if (!previous) { return null; } const data previous.data.map((record) { if (record.id id) { return { foo: bar, ...record, ...values, }; } return record; }); return { ...previous, data, }; }, detail: (previous, values) { if (!previous) { return null; } return { ...previous, data: { foo: bar, ...previous.data, ...values, }, }; }, }, });onMutate 阶段的源码 完整展示了这一机制的执行顺序快照先用queryClient.getQueriesData抓取当前资源下所有查询的数据存入context.previousQueries取消queryClient.cancelQueries取消进行中的请求乐观写入仅当模式不是pessimistic时依次对list/many/one即 detail三类 key 执行setQueriesData。optimisticUpdateMap的默认值为{ list: true, many: true, detail: true }内置合并逻辑是按id匹配记录后执行{ id, ...record, ...values }detail 则是{ ...previous.data, ...values }若键为函数则完全交给函数处理回滚onError中遍历context.previousQueries逐个setQueryData恢复保证请求失败后 UI 回到更新前状态mutationCancelled除外。mutationOptions透传给 useMutationmutationOptions用于向底层useMutation传递选项。注意 Hook 内部已占用了mutationFn与onMutate因此这两项不可覆盖类型定义中被显式Omitconst { mutate, mutation } useUpdate({ resource: products, id: 1, mutationOptions: { retry: 3, onSuccess: (data, variables, context) { // Lets celebrate! }, onError: (error, variables, context) { // An error occurred! }, }, }); mutate({ values: { name: New Product, material: Wood, }, }); if (mutation.isPending) { console.log(Updating product...); } if (mutation.isSuccess) { console.log(Product updated:, mutation.data); }从源码看这些自定义回调在框架默认行为之后被调用onSuccess/onError/onSettled均先执行通知、publish、log 等框架逻辑再调用mutationOptions中的同名回调因此你可以放心地将其用于埋点、跳转等副作用而不会跳过框架的缓存失效。返回值返回一个包含mutation、mutate、mutateAsync与overtime的对象。mutation包含 TanStack QueryuseMutation的全部返回值mutateAsync与mutate参数一致但返回 Promise适合在await链中串联后续操作。overtimeovertime对象中的elapsedTime为已流逝毫秒数请求完成后变为undefinedconst { overtime, mutation } useUpdate(); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... console.log(mutation.isPending); // true/falseAPI ReferenceMutation ParametersPropertyDescriptionTypeDefaultresource *资源名用于 API 数据交互string-id *mutation 函数使用的 idBaseKey-values *mutation 函数的数据值TVariables{}mutationMode决定 mutation 的执行时机pessimistic \| optimistic \| undoablepessimistic*undoableTimeoutmutationMode undoable时执行 mutation 前等待的时长number5000ms*onCancelmutationMode undoable时提供取消 mutation 的函数(cancelMutation: () void) void-successNotification成功 mutation 通知SuccessErrorNotificationSuccessfully updated ${resource}errorNotification失败 mutation 通知SuccessErrorNotificationError when updating ${resource} (status code: ${statusCode})meta传给 dataProvider 的元数据MetaDataQuery{}dataProviderName多个 dataProvider 时指定要使用哪一个stringdefaultinvalidates管理 mutation 结束后发生的缓存失效all,resourceAll,list,many,detail,false[list, many, detail]全局配置这些 prop 在RefineContext中有默认值也可以在Refine组件上统一设置。useUpdate会把Refine传入的值作为默认值但局部mutate参数或 Hook props传入的值优先覆盖。Type ParametersPropertyDescriptionTypeDefaultTDatamutation 结果数据继承BaseRecordBaseRecordBaseRecordTError继承HttpError的自定义错误对象HttpErrorHttpErrorTVariablesmutation 函数的数据值{}{}Return valuePropertyDescriptionTypemutationTanStack Query useMutation 的结果UseMutationResult{ data: TData }, TError, { resource: string; id: BaseKey; values: TVariables; }, UpdateContext*mutatemutation 函数(params?: { resource?: string, id?: BaseKey, values?: TVariables, ... }) voidmutateAsync异步 mutation 函数(params?: { resource?: string, id?: BaseKey, values?: TVariables, ... }) Promise{ data: TData }overtime超时加载信息{ elapsedTime?: number }*UpdateContext为内部类型。小结useUpdate的价值在于把更新一条记录这件看似简单的事收敛成一套与缓存、i18n、实时同步、审计日志深度集成的完整链路pessimistic模式下保证严格的服务端确认后刷新optimistic/undoable模式下借助onMutate快照 setQueriesData写入 onError回滚实现即时 UI 与失败自愈invalidates与optimisticUpdateMap则让你能精确控制哪些缓存被刷新、如何被刷新。所有行为均可在 packages/core/src/hooks/data/useUpdate.ts 中逐段对照验证适合作为理解 Refine 数据层 mutation 体系的入口源码。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考