You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 21:18:22