封装react-query的useMutation时TypeScript类型推断异常问题
封装react-query的useMutation为自定义钩子时,初始代码TypeScript能正确推断类型:
function myCustomMutation() { return useMutation({ mutationFn: (variables: CustomVariablesType): Promise<CustomResponseType> { // 业务逻辑 } }) }
但当尝试允许传入额外的react-query配置,引入UseMutationOptions类型后,出现类型错误:
import { useMutation, UseMutationOptions } from 'react-query'; function myCustomMutation(config: UseMutationOptions) { return useMutation({ ...config, mutationFn: (variables: CustomVariablesType): Promise<CustomResponseType> { // 业务逻辑 } }) }
错误提示:
Argument of type '{ useErrorBoundary?: boolean | ((error: unknown) => boolean) | undefined; mutationFn: ((variables: CustomVariablesType) => Promise
) | MutationFunction<unknown, void>; mutationKey?: MutationKey | undefined; ... 6 more ...; meta?: MutationMeta | undefined; }' is not assignable to parameter of type 'MutationKey'
看起来TypeScript选错了useMutation的重载。
将配置单独提取指定类型后,又出现新错误:
import { useMutation, UseMutationOptions } from 'react-query'; function myCustomMutation(config: UseMutationOptions) { const newConfig:UseMutationOptions = { ...config, mutationFn: (variables: CustomVariablesType): Promise<CustomResponseType> { // 业务逻辑 } }; return useMutation(newConfig); }
错误提示:
Type '(variables: CustomVariablesType): Promise
' is not assignable to type 'MutationFunction<unknown, void>'
查看useMutation的类型声明:
declare function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(options: UseMutationOptions<TData, TError, TVariables, TContext>): UseMutationResult<TData, TError, TVariables, TContext>;
其中UseMutationOptions的mutationFn类型为MutationFunction<TData, TVariables>,推测是因为未传入泛型参数,UseMutationOptions使用了默认的unknown和void类型导致的,需要解决如何在允许传入自定义配置的同时,让TypeScript正确推断类型。
1. 显式指定基础泛型参数
为自定义钩子的UseMutationOptions明确指定你的业务类型,同时用Omit排除mutationFn(避免外部覆盖内部固定的函数逻辑):
import { useMutation, UseMutationOptions, UseMutationResult } from 'react-query'; // 提前定义你的业务类型 type CustomVariablesType = { /* 变量类型定义 */ }; type CustomResponseType = { /* 响应类型定义 */ }; function myCustomMutation( config?: Omit<UseMutationOptions<CustomResponseType, unknown, CustomVariablesType>, 'mutationFn'> ): UseMutationResult<CustomResponseType, unknown, CustomVariablesType> { return useMutation({ ...config, mutationFn: (variables: CustomVariablesType): Promise<CustomResponseType> => { // 你的业务逻辑,比如API请求 return fetch('/api/your-endpoint', { method: 'POST', body: JSON.stringify(variables) }).then(res => res.json()); } }); }
- 显式指定泛型后,TypeScript会正确识别
mutationFn的参数和返回值类型,不会再使用默认的unknown和void Omit确保外部传入的配置无法覆盖内部的mutationFn,保证钩子的核心逻辑固定
2. 允许自定义错误和上下文类型(可选)
如果需要让调用方自定义错误类型(TError)和上下文类型(TContext),可以把自定义钩子改成泛型函数:
import { useMutation, UseMutationOptions, UseMutationResult } from 'react-query'; type CustomVariablesType = { /* 变量类型定义 */ }; type CustomResponseType = { /* 响应类型定义 */ }; function myCustomMutation< TError = unknown, TContext = unknown >( config?: Omit<UseMutationOptions<CustomResponseType, TError, CustomVariablesType, TContext>, 'mutationFn'> ): UseMutationResult<CustomResponseType, TError, CustomVariablesType, TContext> { return useMutation({ ...config, mutationFn: (variables: CustomVariablesType): Promise<CustomResponseType> => { // 业务逻辑 return fetch('/api/your-endpoint', { method: 'POST', body: JSON.stringify(variables) }).then(res => res.json()); } }); }
这样调用方可以根据需求传入自定义的错误和上下文类型,同时保持mutationFn的类型固定。
3. 解决重载匹配问题
之前出现的重载选错问题,本质是未指定泛型的UseMutationOptions会被TypeScript误判为兼容MutationKey的类型(useMutation有一个直接传mutationKey的重载)。显式指定泛型参数后,TypeScript会自动匹配接收UseMutationOptions的正确重载。
内容的提问来源于stack exchange,提问作者rbhalla

