如何用React Query与TypeScript实现文件上传Hook并修复类型错误
解决React Query Mutation结合Axios取消请求的TypeScript错误
问题根源
你的uploadVideo函数返回的是包含Promise和取消函数的数组,但React Query的MutationFunction要求函数必须返回Promise,而非数组。强制类型转换as unknown as MutationFunction会掩盖类型不匹配的问题,最终触发TypeScript报错。
解决方案
1. 调整API函数的返回结构
将返回的数组改为类型明确的对象,让类型系统可以正确推断:
// 定义返回类型 type UploadVideoResult<T = any> = { promise: Promise<T>; cancel: (msg: string) => void; }; type UploadVideoParams = { videofile: File channelId: string | number name: string } const uploadVideo = ( { videofile, channelId, name }: UploadVideoParams, onUploadProgress: (...args: any) => void ): UploadVideoResult => { const formData = new FormData() appendFormDataIfTruthy(formData, 'videofile', videofile) appendFormDataIfTruthy(formData, 'channelId', channelId) appendFormDataIfTruthy(formData, 'name', name) const source = CancelToken.source() const requestPromise = axiosInstance({ url: '/videos/upload', method: 'POST', headers: { 'Content-Type': 'multipart/form-data' }, data: formData, onUploadProgress, cancelToken: source.token, }).then(res => res.data) return { promise: requestPromise, cancel: (msg: string) => source.cancel(msg) } }
2. 正确实现React Query Mutation
在mutation函数内部调用uploadVideo,返回其Promise,并保存取消函数以便外部调用:
import { useMutation, useQueryClient } from 'react-query'; export const useUploadMutation = () => { const queryClient = useQueryClient(); let cancelUpload: ((msg: string) => void) | undefined; const mutation = useMutation( async (params: UploadVideoParams) => { // 调用API并保存取消函数 const { promise, cancel } = VideosService.uploadVideo(params, (...args) => { // 若需处理上传进度,可在此处添加逻辑 }); cancelUpload = cancel; return promise; }, { onSuccess: () => { queryClient.invalidateQueries([VideosService.getVideos.name]); queryClient.invalidateQueries([VideosService.getChannelVideos.name]); }, // 请求结束后清空取消函数引用 onSettled: () => { cancelUpload = undefined; } } ); // 将取消函数附加到mutation实例,方便外部调用 return { ...mutation, cancelUpload: (msg = '上传已取消') => cancelUpload?.(msg) }; };
3. 使用方式
在组件中调用mutation时,可直接使用cancelUpload方法取消请求:
const { mutate, cancelUpload } = useUploadMutation(); // 触发上传 mutate({ videofile, channelId, name }); // 取消上传 cancelUpload('用户主动取消上传');
替代方案:在Mutation内部管理CancelToken
如果不想修改原有API函数,也可以将CancelToken的逻辑移到mutation内部,让mutation直接返回Promise:
export const useUploadMutation = () => { const queryClient = useQueryClient(); return useMutation( async (params: UploadVideoParams & { onUploadProgress?: (...args: any) => void }) => { const source = CancelToken.source(); const formData = new FormData(); appendFormDataIfTruthy(formData, 'videofile', params.videofile); appendFormDataIfTruthy(formData, 'channelId', params.channelId); appendFormDataIfTruthy(formData, 'name', params.name); // 定义带取消方法的Promise类型 type CancelablePromise<T> = Promise<T> & { cancel: (msg: string) => void }; const requestPromise = axiosInstance({ url: '/videos/upload', method: 'POST', headers: { 'Content-Type': 'multipart/form-data' }, data: formData, onUploadProgress: params.onUploadProgress, cancelToken: source.token, }).then(res => res.data) as CancelablePromise<any>; // 给Promise添加取消方法 requestPromise.cancel = (msg: string) => source.cancel(msg); return requestPromise; }, { onSuccess: () => { queryClient.invalidateQueries([VideosService.getVideos.name]); queryClient.invalidateQueries([VideosService.getChannelVideos.name]); }, } ); };
使用时可通过mutation.mutateAsync返回的Promise调用取消:
const { mutateAsync } = useUploadMutation(); let uploadPromise: CancelablePromise<any> | undefined; const handleUpload = async () => { uploadPromise = mutateAsync({ videofile, channelId, name }); try { await uploadPromise; } catch (err) { // 处理错误 } }; const handleCancel = () => { uploadPromise?.cancel('用户取消上传'); };
内容的提问来源于stack exchange,提问作者Bahar
相关产品推荐
相关产品推荐

