Antd Upload 组件 通过customRequest发送xhr请求的使用并控制进度条进度

468 阅读2分钟

最近做的项目需要使用原始的xhr方法,上传文件到阿里云。同时需要展示进度条的进度。因此需要自己来控制上传行为。通过查阅Antd API 文档,找到 customRequest,根据文档,这个API可以 通过覆盖默认的上传行来自定义自己的上传。这个到底应该怎么用呢?
首先先去看了一眼customRequest的的源代码,如下:

    //这里是Uploader组件接收的props, 从中我们可以看到customRequest这个API接受
    //一个RcCustomRequestOptions类型的参数。
    export interface UploadProps<T = any> extends Pick<RcUploadProps, 'capture'> {
        ...
        fileList?: Array<UploadFile<T>>;
        action?: string | ((file: RcFile) => string) | ((file: RcFile) => PromiseLike<string>);
        method?: 'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch';
        onChange?: (info: UploadChangeParam<UploadFile<T>>) => void;
        ...
        customRequest?: (options: RcCustomRequestOptions) => void;
        ...
    }
    
    //那么RcCustomRequestOptions 又有什么属性呢?
    //继续看源码,可以看到 customRequest 中的 options 的属性。
    //看到这里 我们可以猜想,自定义的上传接口返回的时候:
    //这里的 onSuccess,onError,onProgress看起来比较有用。
    //经过尝试,发现调用任意这三个函数,都会再次触发Uploader组件中onChange事件。
    //通过这个事件的再次触发,我们可以获取已经上传的文件列表(受控)fileList的数据。
    //这个数据里面有我们需要的status,percentage之类的有用信息。
    export interface UploadRequestOption<T = any> {
    onProgress?: (event: UploadProgressEvent) => void;
    onError?: (event: UploadRequestError | ProgressEvent, body?: T) => void;
    onSuccess?: (body: T, xhr?: XMLHttpRequest) => void;
    data?: Record<string, unknown>;
    filename?: string;
    file: Exclude<BeforeUploadFileType, File | boolean> | RcFile;
    withCredentials?: boolean;
    action: string;
    headers?: UploadRequestHeader;
    method: UploadRequestMethod;
}

在这个需求中,需要通过发送接口请求来动态获取上传文件接口需要的URL。这个问题可以通过下面例子中的action,返回一个promise的方式实现。
最后 我们需要考虑一下怎么来处理进度条这个问题。xhr提供给我们一个 onprogress的方法/事件. 将xhr的onprogress和我们上文提到的RcCustomRequestOptions提供的onProgress结合在一起,就可以在onChange事件中得到onProgress的数据。

下面是实现此类需求的代码,代码中的注释进一步解释一些其他的细节问题。 希望这篇文章可以帮到读者去解决类似的问题。

 const [uploadFileList, setUploadFileList] = useState<UploadFile[]>([]);

 const props: UploadProps = {
    name: 'file',
    multiple: true,
    progress: { strokeWidth: 0, showInfo: false },
    action: (file) => {
      const { name } = file;
      return new Promise((resolve, reject) => {
        getOssUrlFromWeb({ fileName: name }).then((ossUrlres) => {
          if (ossUrlres.success) {
              //这里数据返回的是 {url,fileId}
            resolve(JSON.stringify(ossUrlres.data));
          }
        });
      });
    },
    fileList: uploadFileList,
    onChange: (info) => {
      //展示上传的文件的列表,customRequest里面的onSucess,onProgress之类的回调函数被调用的时候,这里也会再次被调用。
      setUploadFileList(info.fileList);
    },

    customRequest: (options) => {
      const { fileId = '', url = '' } = JSON.parse(options.action);
      const xhr = new XMLHttpRequest();

      xhr.open('PUT', url, true);
      xhr.setRequestHeader('Content-Type', 'application/octet-stream');
      xhr.withCredentials = false;

      xhr.onload = function () {
        setFinishedUpload(false);

        if (xhr.readyState === 4 && xhr.status === 200) {
          notifyOssDirectFinishedFromWeb({ fileId })
            .then((res) => {
              if (res.success) {
                //与使用axios不同,这里需要传入xhr
                options.onSuccess({ success: true }, xhr);
                //控制成功,如果漏写 进度条会一直转
                message.success(language.controller_uploadSuccess);
              }else{
                options.onError({
                  msg: "upload failed"
                });
              }
            })
            .catch((err) => {
              message.error(
                language.js_upfile_list_upFailure + err?.message ||
                  language.service_failure,
              );
              //控制错误情况 如果没写 进度条会一直转
              options.onError({
                msg: err?.message || "upload failed"
              });
            });
        } else {
          message.success(language.controller_uploadSuccess);
        }
      };
      //xhr 有upload 和download 如果是donwload 就直接写 xhr.onprogress
      xhr.upload.onprogress = (event) => {
        const percentage = Math.ceil((event.loaded / event.total) * 100);
        //控制进度条进度
        options.onProgress({ percent: percentage });
      };
      xhr.send(options.file);
    },
  };