最近做的项目需要使用原始的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);
},
};