我正在尝试使用 KendoReact Upload 组件制作可重用的多文件上传组件。我所拥有的是功能性的,但我对 React 还很陌生,所以希望有一种更简洁的方法来实现这一点。
默认情况下,该组件接受上传/删除请求的端点 URL,但 saveUrl/removeUrl 道具也可以采用允许操作上传文件的函数。我正在使用 FileReader 将文件转换为 base64,并在上传文件后采用可用于外部状态更新的函数。
我有以下实现,但感觉很重,因为我需要
- Kendo 控件使用的文件状态中的 UploadFileInfo 集合
- 包含要传递的 base64 内容的文件的状态中的 IFileUploadViewModel 集合
- 用于触发外部状态更新的 useEffect 调用,以确保传递更新的状态
- saveRequestPromise 和 removeRequestPromise 中的函数状态更新,以保持控制和外部文件集合同步
import React, { useEffect, useState } from "react";
import { Upload, UploadFileInfo, UploadOnAddEvent, UploadOnProgressEvent, UploadOnRemoveEvent, UploadOnStatusChangeEvent } from "@progress/kendo-react-upload";
export interface IFileUploadViewModel {
uid: string;
fileName: string;
fileSize: number;
content?: string | undefined;
}
export function KendoMultipleSmallFileUpload(props: {
allowedExtensions?: string[] | undefined;
minFileSizeMb?: number | undefined;
maxFileSizeMb?: number | undefined;
onFileChange: (files: Array<IFileUploadViewModel>) => void;
}) {
// The Upload component accepts files from users only, an initial 'system' state e.g. pre-selected file determined by a viewmodel is not a valid use case.
const [files, setFiles] = useState(new Array<UploadFileInfo>());
const [fileUploads, setFileUploads] = useState(new Array<IFileUploadViewModel>());
useEffect(() => {
console.log(fileUploads);
props.onFileChange(fileUploads);
}, [fileUploads]);
const convertMbToBytes = (mb: number) => mb * 1024 * 1024;
// The Upload component does not allow empty files, convert file size restrictions to bytes and set a minimum value if not provided.
const minFileSizeBytes = props.minFileSizeMb ? convertMbToBytes(props.minFileSizeMb) : 1;
const maxFileSizeBytes = props.maxFileSizeMb ? convertMbToBytes(props.maxFileSizeMb) : undefined;
// Fires when user clicks on the Remove button while the file upload is in progress. Can be used when the saveUrl option is set to a function that cancels custom requests.
// For small files, conversion to a byte array is so quick it is impossible to Cancel through the UI and in any case is automatically followed by Remove so no implementation is required.
// For large files we may need to consider aborting the upload process through the FileReader API.
function onCancel(event: any) {}
function onAdd(event: UploadOnAddEvent) {
setFiles(event.newState);
}
function onRemove(event: UploadOnRemoveEvent) {
setFiles(event.newState);
}
function onProgress(event: UploadOnProgressEvent) {
setFiles(event.newState);
}
function onStatusChange(event: UploadOnStatusChangeEvent) {
setFiles(event.newState);
}
function onSaveRequest(
files: UploadFileInfo[],
options: { formData: FormData; requestOptions: any },
onProgress: (uid: string, event: ProgressEvent<EventTarget>) => void
): Promise<{ uid: string }> {
const currentFile = files[0] as UploadFileInfo;
const uid = currentFile.uid;
console.log("onSaveRequest for " + currentFile.name);
const saveRequestPromise = new Promise<{ uid: string }>(async (resolve, reject) => {
// as currently configured it is impossible to request a save from the UI if there are validation errors, but that can be changed so keeping this check in place
if (currentFile.validationErrors && currentFile.validationErrors.length > 0) {
reject({ uid: uid });
} else {
const reader = new FileReader();
// onload is executed when the load even is fired i.e. when content read with readAsArrayBuffer, readAsBinaryString, readAsDataURL or readAsText is available
reader.onload = () => {
if (reader.result && typeof reader.result === "string") {
// stripping the data-url declaration as per https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
const base64Result = reader.result.split(",")[1];
// update viewModel and resolve
const fileUpload: IFileUploadViewModel = { uid: uid, fileName: currentFile.name, fileSize: currentFile.size!, content: base64Result };
setFileUploads((state) => [...state, fileUpload]);
resolve({ uid: uid });
} else {
reject({ uid: uid });
}
};
// onprogress is fired periodically as the FileReader reads data and the ProgressEvent can be passed directly to the Upload control, handy!
reader.onprogress = (data) => {
onProgress(uid, data);
};
// if the read is not completed due to error or user intervention, reject
reader.onabort = () => {
reject({ uid: uid });
};
reader.onerror = () => {
reject({ uid: uid });
};
reader.readAsDataURL(currentFile.getRawFile!());
}
});
return saveRequestPromise;
}
function onRemoveRequest(files: UploadFileInfo[], options: { formData: FormData; requestOptions: any }): Promise<{ uid: string }> {
const currentFile = files[0] as UploadFileInfo;
const uid = currentFile.uid;
const removeRequestPromise = new Promise<{ uid: string }>((resolve) => {
const updatedFileUploads = fileUploads.filter((f) => f.uid !== uid);
setFileUploads(updatedFileUploads);
props.onFileChange(updatedFileUploads);
resolve({ uid: uid });
});
return removeRequestPromise;
}
return (
<>
<Upload
className="mb-2"
autoUpload={true}
batch={false}
files={files}
multiple={true}
onAdd={onAdd}
onCancel={onCancel}
onProgress={onProgress}
onRemove={onRemove}
onStatusChange={onStatusChange}
withCredentials={false}
removeUrl={onRemoveRequest}
saveUrl={onSaveRequest}
restrictions={{ allowedExtensions: props.allowedExtensions, minFileSize: minFileSizeBytes, maxFileSize: maxFileSizeBytes }}
/>
<div className="d-flex flex-row flex-wrap small">
{props.minFileSizeMb && (
<div className="d-flex flex-row mr-2 mb-2">
<span className="label font-weight-bold mr-1">Minimum File Size: </span>
<span>{props.minFileSizeMb} MB.</span>
</div>
)}
{props.maxFileSizeMb && (
<div className="d-flex flex-row mr-2 mb-2">
<span className="label font-weight-bold mr-1">Maximum File Size: </span>
<span>{props.maxFileSizeMb} MB.</span>
</div>
)}
{props.allowedExtensions && (
<div className="d-flex flex-row mr-2 mb-2">
<span className="label font-weight-bold mr-1">Supported File Types: </span>
<span>{props.allowedExtensions.join(", ")}.</span>
</div>
)}
</div>
</>
);
}
任何建议,将不胜感激。