Refine 的 useCustom Hook 完全指南:自定义查询、配置参数与实现原理
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
useCustom是 Refine v5 中用于发送自定义查询请求的数据 Hook,它基于 TanStack Query 的useQuery扩展而来,将url、method、config等参数透传给数据提供者(dataProvider)的custom方法。本文以仓库内官方文档与源码为依据,系统讲解useCustom的全部属性、返回值与底层实现机制,帮助你对接自定义 API 端点、执行唯一性校验、调用报表接口等无法用标准 CRUD Hook 覆盖的场景,并掌握查询失效(invalidate)与超时提示的正确姿势。
useCustom 是什么
useCustom是 Refine 中面向“自定义请求”的查询 Hook。它本质上是 TanStack QueryuseQuery的扩展版本:不仅完整继承useQuery的全部能力(缓存、重试、轮询、select转换等),还额外提供了 Refine 生态特有的能力——通知(NotificationProvider)、鉴权错误回调(onError)、meta 合并、加载超时(overtime)等。
从源码可以看到它的核心职责,见 packages/core/src/hooks/data/useCustom.ts:
if (custom) { const queryResponse = useQuery<...>({ queryKey: keys() .data(dataProviderName) .mutation("custom") .params({ method, url, ...config, ...(preferredMeta || {}) }) .get(), queryFn: (context) => custom<TQueryFnData>({ url, method, ...config, meta: { ...combinedMeta, ...prepareQueryContext(context as any) }, }), ...queryOptions, }); }也就是说,useCustom的查询函数(queryFn)就是 dataProvider 上的custom方法。只要在传给<Refine>的 dataProvider 上实现了custom,useCustom就能正常工作;如果 dataProvider 没有实现custom,Hook 会直接抛出错误Not implemented custom on data provider.(这一点在测试用例 packages/core/src/hooks/data/useCustom.spec.tsx 中有明确验证)。
何时不该使用 useCustom
:::caution 使用场景警告useCustom不应用于资源的增、删、改操作。创建、更新、删除请分别使用 useCreate、useUpdate、useDelete。 :::
原因在于:useCustom与其他数据 Hook 不同,它不会自动失效(invalidate)相关查询,因此也不会主动刷新应用状态。如果你需要自定义的是“变更类”请求,请改用 useCustomMutation Hook。
基本用法
useCustom要求必传url和method两个属性,它们会作为参数传给 dataProvider 的custom方法;当这些属性发生变化时,Hook 会触发一次新的请求(得益于 TanStack Query 的 queryKey 机制)。
典型用法如下,先通过useApiUrl拿到当前 dataProvider 的 API 基础地址,再拼接自定义端点:
import { useCustom, useApiUrl } from "@refinedev/core"; interface PostUniqueCheckResponse { isAvailable: boolean; } const apiUrl = useApiUrl(); const { query } = useCustom<PostUniqueCheckResponse>({ url: `${apiUrl}/posts-unique-check`, method: "get", config: { headers: { "x-custom-header": "foo-bar", }, }, });useApiUrl内部会读取当前资源对应的 dataProvider(可通过参数指定名称),并调用其getApiUrl方法返回基础 URL,见 packages/core/src/hooks/data/useApiUrl.ts。
Properties 详解
url(必填)
url会被直接传给 dataProvider 的custom方法,通常用于指定请求的端点地址:
useCustom({ url: "www.example.com/api/get-products", });method(必填)
method指定 HTTP 方法。在源码中,method的类型被严格限制为以下七种之一,见 packages/core/src/hooks/data/useCustom.ts:
method: "get" | "delete" | "head" | "options" | "post" | "put" | "patch";useCustom({ method: "get", });config.headers
用于指定请求头,会原样透传给custom方法:
useCustom({ config: { headers: { "x-custom-header": "foo-bar", }, }, });config.query
用于指定查询参数(query string):
useCustom({ config: { query: { title: "Foo bar", }, }, });config.payload
用于指定请求体(body)。例如调用post或put类自定义端点时携带数据:
useCustom({ config: { payload: { title: "Foo bar", }, }, });config.sorters
用于发送排序参数,字段结构与getList的排序一致:
useCustom({ config: { sorters: [ { field: "title", order: "asc", }, ], }, });config.filters
用于发送过滤参数,支持 Refine 的条件运算符(如contains):
useCustom({ config: { filters: [ { field: "title", operator: "contains", value: "Foo", }, ], }, });在源码中,config的完整类型为UseCustomConfig,包含sorters、filters、query、payload、headers五个可选字段,见 packages/core/src/hooks/data/useCustom.ts。
queryOptions
queryOptions用于向底层的useQuery透传额外选项,例如调整重试次数、禁用自动请求等。它与 TanStack QueryuseQuery的选项保持一致:
useCustom({ queryOptions: { retry: 3, enabled: false, }, });注意一个细节:Refine 在内部已经为你生成了 queryKey,因此queryOptions中的queryKey与queryFn在类型上被设计为可选(以便向后兼容),见 packages/core/src/hooks/data/useCustom.ts。当你主动传入queryKey时,它会覆盖默认的自动生成的 key——这一点在测试用例“with custom query key”中有验证,见 packages/core/src/hooks/data/useCustom.spec.tsx。
meta
meta是一个特殊属性,用于向 dataProvider 方法传递额外信息,常见用途有:
- 针对特定用例定制 dataProvider 方法的行为;
- 用纯 JavaScript 对象(JSON)生成 GraphQL 查询。
在下面的示例中,meta作为参数传入 dataProvider 的custom方法:
useCustom({ meta: { foo: "bar", }, }); const myDataProvider = { //... custom: async ({ url, method, sort, filters, payload, query, headers, meta, }) => { const foo = meta?.foo; console.log(foo); // "bar" //... }, //... };从实现上看,meta还会与当前路由参数、资源级 meta 合并:源码中通过getMeta合并显式传入的meta与资源上下文 meta,并在 queryFn 里与 TanStack Query 的查询上下文一起传给custom,见 packages/core/src/hooks/data/useCustom.ts。测试用例验证了meta会与路由参数baz: "qux"合并后传给custom方法,见 packages/core/src/hooks/data/useCustom.spec.tsx。
dataProviderName
当应用配置了多个 dataProvider 时,用dataProviderName指定本次请求使用哪一个:
useCustom({ dataProviderName: "second-data-provider", });successNotification
成功通知定制。需要先配置 NotificationProvider此属性才生效。请求成功且useCustom调用 NotificationProvider 的open函数时,会使用这里返回的配置:
useCustom({ successNotification: (data, values) => { return { message: `${data.title} Successfully fetched.`, description: "Success with no errors", type: "success", }; }, });源码中,成功通知的处理逻辑位于 packages/core/src/hooks/data/useCustom.ts:当queryResponse.isSuccess且存在数据时,如果successNotification是函数,则以(数据、合并后的 config 与 meta)为参数调用它;返回false则可静默跳过通知(测试用例“should not call open from notification provider on return false”验证了这一点)。
errorNotification
失败通知定制,同样依赖 NotificationProvider:
useCustom({ errorNotification: (data, values) => { return { message: `Something went wrong when getting ${data.id}`, description: "Error", type: "error", }; }, });需要留意的是:即使你没有传errorNotification,Refine 仍会为失败请求弹出一条默认错误通知(key 为${method}-notification,例如get-notification,message 为Error (status code: ...)),相关逻辑见 packages/core/src/hooks/data/useCustom.ts。测试用例 packages/core/src/hooks/data/useCustom.spec.tsx 也断言了默认错误通知的内容。
overtimeOptions
用于请求加载超时的场景——当请求耗时过长时展示加载提示。interval是时间间隔(毫秒),onInterval会在每个间隔触发一次:
const { overtime } = useCustom({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // You can use it like this: { elapsedTime >= 4000 && <div>this takes a bit longer than expected</div>; }返回的overtime.elapsedTime表示已耗时(毫秒),请求完成时变为undefined。测试用例验证了interval与onInterval的行为:请求挂起期间elapsedTime持续累加,请求完成后重置为undefined,见 packages/core/src/hooks/data/useCustom.spec.tsx。
返回值
useCustom返回一个对象,源码定义见 packages/core/src/hooks/data/useCustom.ts:
| 名称 | 说明 | 类型 |
|---|---|---|
query | TanStack Query 的查询结果对象 | QueryObserverResult<CustomResponse<TData>, TError> |
result | 便捷取数对象,result.data即响应数据 | { data: CustomResponse<TData>["data"] } |
overtime | 加载超时信息,{ elapsedTime?: number } | { elapsedTime?: number } |
其中CustomResponse的定义为{ data: TData },见 packages/core/src/contexts/data/types.ts。因此你可以用query.data?.data或result.data两种方式读取真正的响应数据。
类型参数(Type Parameters)
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
TQueryFnData | 查询函数返回的数据类型,继承自BaseRecord | BaseRecord | BaseRecord |
TError | 自定义错误对象,继承自HttpError | HttpError | HttpError |
TQuery | 查询参数的类型 | TQuery | unknown |
TPayload | 请求体参数的类型 | TPayload | unknown |
TData | select函数返回的数据类型,继承自BaseRecord;未指定时默认取TQueryFnData | BaseRecord | TQueryFnData |
说明:
BaseRecord与HttpError均为 Refine 核心接口,定义可参考 packages/core/src/contexts/data/types.ts。
源码视角:useCustom 的底层执行链路
结合源码与测试,可以梳理出useCustom的完整执行链路:
- 取 dataProvider:通过
useDataProvider按dataProviderName(缺省为当前资源关联的 provider)解析出 provider,并取出其custom方法; - 生成 queryKey:Refine 使用
keys().data(dataProviderName).mutation("custom").params({ method, url, ...config, ...meta }).get()自动构建稳定的查询键——这意味着只要url、method、config或meta变化,queryKey 就会变化并触发重新请求; - 执行查询:queryFn 调用
custom,并额外传入合并了 meta 与查询上下文的信息; - 通知与鉴权:成功时触发成功通知;失败时先调用鉴权提供者的
onError/checkError处理(401 等场景),再触发错误通知; - 超时跟踪:通过
useLoadingOvertime基于queryResponse.isFetching驱动overtime.elapsedTime; - 无 custom 时抛错:如果 dataProvider 未实现
custom,抛出Not implemented custom on data provider.。
与之对应的参数契约是CustomParams,见 packages/core/src/contexts/data/types.ts:它定义了url、method、sorters?、filters?、payload?、query?、headers?、meta?等字段,这也是实现自定义 dataProvider 时custom方法的标准入参。
真实数据提供者如何实现 custom:以 simple-rest 为例
以仓库内置的@refinedev/simple-rest为例,其custom实现(见 packages/simple-rest/src/provider.ts)展示了sorters、filters、query如何被拼接为查询字符串:排序会转换为_sort/_order参数,过滤会通过generateFilter生成查询串,query直接 stringify 追加到 URL,随后按method分发到 axios 请求。这解释了useCustom传入的config在真实 REST 场景下的落点——你可以基于此模式编写自己的custom实现,也可以直接使用 simple-rest 获得开箱即用的自定义请求支持。
FAQ:如何使自定义查询失效(invalidate)
由于useCustom不会自动失效查询,当你需要主动刷新数据(例如某个自定义查询的依赖数据被更新后)时,可以使用 TanStack Query 的useQueryClient提供的invalidateQueries方法:
import { useQueryClient } from "@tanstack/react-query"; const queryClient = useQueryClient(); queryClient.invalidateQueries(["custom-key"]);请注意,你需要知道该查询的 queryKey才能使其失效。如果你不清楚默认生成的 key,可以通过queryOptions.queryKey为useCustom显式指定一个稳定的 key:
import { useCustom } from "@refinedev/core"; useCustom({ queryOptions: { queryKey: ["custom-key"], }, });之后无论何时调用invalidateQueries(["custom-key"]),都能精准命中并刷新该自定义查询。
小结
useCustom是 Refine 中对接自定义端点的首选查询 Hook,完整继承 TanStack QueryuseQuery的能力,并叠加 Refine 的通知、鉴权、meta 与超时机制。- 全部请求参数通过
url、method、config(headers/query/payload/sorters/filters)与meta传递给 dataProvider 的custom方法,参数变化会自动触发重新请求。 - 增删改场景请使用
useCreate/useUpdate/useDelete,需要自定义变更请求时使用useCustomMutation。 - 手动刷新自定义查询时,通过
queryOptions.queryKey固定 key,再配合queryClient.invalidateQueries精准失效。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考