我是如何构建自定义图像文件上传器的
整个问题源于我尝试使用 Cloudinary 文件上传器时遇到的一些问题。在花了几个小时尝试配置但最终失败后,我决定为我正在进行的项目构建自己的自定义图像文件上传器。
一开始我完全不知道该怎么实现,但我知道我总能找到办法。(大多数开发者不都是这样吗?😀)
这是一个使用 Next.js 和 TypeScript 的 React 项目。您可能需要创建一个 Next.js 项目来跟随教程进行操作,并从 npm 安装一些库来快速上手⚾。
npm i react-dropzone
让我们创建一个名为 `<component_name>` 的 React 组件UploadImageFiles(哎呀,我不太擅长起名字,请见谅😋)。以下是该组件的基本框架以及我们需要用到的一些状态:
export default function UploadImageFiles() {
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const [fileList, setFileList] = useState<FileUpload[]>([]);
const [fileError, setFileError] = useState<string | null>(null);
return (
<>
<main>
{/* some UIish 😄 JSX*/}
</main>
</>
)
}
为了更深入地探讨这个问题,我喜欢把我的实现思路整理成一个列表。你可以把它看作是伪代码。🤷
- 处理用户文件选择
- 验证文件
- 显示文件列表(我称之为暂存区🎬)
让我们借助 TypeScript 来处理文件选择,这样就不会破产了。😄
function handleFileChange(e: ChangeEvent<HTMLInputElement>) {
const inputFiles = Array.from(e.target.files || []);
const validatedFiles: File[] = [];
files.forEach((file) => {
if (!file.type.startsWith("image/")) { // check for valid image file
setFileError("File should be an image");
} else if (file.size > 1024 * 1024 * 25) { // check for image size
setFileError("File max size is 24MB");
} else {
validatedFiles.push(file);
}
});
if (validatedFiles.length > 0) {
setSelectedFiles(validatedFiles);
setFileError(null);
}
}
该handleFileChange()函数用于文件选择,并验证文件类型和图像文件大小是否符合预期。如果不符合,则会设置 fileError 状态。如果所有检查都通过,则将文件添加到 validatedFiles 数组中。代码的最后一部分使用 setSelectedFiles 操作和 validatedFiles 列表来设置 selectedFiles 状态。
🚫🚫🚫 文件上传功能在哪里?
干得好:
interface FileUpload {
url: string;
name: string;
size: number;
}
文件选择和验证完成后,我们来做一些准备工作……🎬
为了向暂存区添加新文件,我们的暂存功能将位于 useEffect 中,以便在选中新文件时将其添加进去。
useEffect(() => {
function stagingFiles() {
if (selectedFiles.length) {
const imageFile = selectedFiles.map((file: File) => {
return {
url: URL.createObjectURL(file),
name: file.name,
size: file.size,
};
});
setFileList((curState) => {
const newImageFile = imageFile.filter((file) => !curState.some((item) => item.name === file.name))
return [...curState, ...newImageFile];
});
}
}
stagingFiles();
}, [selectedFiles]);
在暂存区,我们需要图片 URL、名称和尺寸。我们使用 URL 来显示图片。由于我们的数据selectedFiles只是一个图片列表,我们遍历它并从中返回所需的属性(如上所述),将其作为对象返回。`createObjectURL ()` 函数URL.createObjectURL()有助于创建图片 URL 。
setFileList((curState) => {
const newImageFile = imageFile.filter((file) => !curState.some((item) => item.name === file.name))
return [...curState, ...newImageFile];
});
暂存功能的第二部分是设置一个独特的图像,确保不会向暂存区添加重复的图像文件。
不要忘记将其添加
selectedFiles到useEffect依赖项数组中。
我们可能需要从暂存区删除一些文件。方法快捷简便:
function removeFromList(item: number) {
setFileList((curState) => {
return curState.filter((_, index) => index !== item);
});
}
您可能想知道,我们的拖放逻辑在哪里?稍等片刻……
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop: (files: File[]) => {
processFiles(files);
},
multiple: true,
noClick: true, // avoid duplicate opening of file selection ui
});
React Dropzone 为我们提供了一个useDropzone实现拖放功能的钩子。通过这个钩子,我们可以解构所需的属性。更多信息请查看其官方文档:react-dropzone。
我们将对现有handleFileChange函数进行一些更新,将其部分功能移至另一个processFiles函数中:
function processFiles(files: File[]) {
const validatedFiles: File[] = [];
files.forEach((file) => {
if (!file.type.startsWith("image/")) {
setFileError("File should be an image");
} else if (file.size > 1024 * 1024 * 25) {
setFileError("File max size is 24MB");
} else {
validatedFiles.push(file);
}
});
if (validatedFiles.length > 0) {
setSelectedFiles(validatedFiles);
setFileError(null);
}
}
最后再做两处修改。分别在根元素和输入元素中添加 `<head> getRootProps` 和`<body>`:getInputProps
return (
<div>
<div {...getRootProps()}>
<input {...getInputProps()} onChange={handleFileChange} />
{isDragActive ? <p>Drop the files here ...</p> : <p>Drag 'n' drop some files here, or click to select files</p>}
</div>
{fileError && <p>{fileError}</p>}
<ul>
{fileList.map((file, index) => (
<li key={index}>
<img src={file.url} alt={file.name} width={50} />
<button onClick={() => removeFromList(index)}>Remove</button>
</li>
))}
</ul>
</div>
);
这是我目前的实现版本。
希望你和我一样玩得开心。欢迎你添加自己的功能。
文章来源:https://dev.to/bolajbolajoko51/how-i-built-a-custom-image-file-uploader-5aje
