Antd中Upload组件封装及使用:

1.Upload上传组件功能:

文件校验 :

文件格式校验/文件大小校验/上传文件总个数校验

相关功能 :

拖拽功能/上传到远程(七牛)/文件删除及下载

2.组件效果展示:

3.疑难点及解决方案:

Promise.all多文件并行上传到远程(七牛云):

(1)在beforeUpload钩子函数中获取token

(2)循环fileList文件列表,使用fetch将所有文件上传到七牛,并将结果包装成Promise return出去

(3)待所有文件上传成功后,通过Promise.all获取并存储结果,并通过useEffect及时将七牛返回的结果添加到fileList文件列表中。

(4)注:由于一次上传多个文件时,beforeUpload钩子函数会执行多次,需要使用debounce进行防抖。

复制代码
const [promiseAllResult, setPromiseAllResult] = useState<UploadFile[]>([]);

const beforeUpload: UploadProps["beforeUpload"] = (file, fileList) => {
		debouncedBeforeUpload(fileList);
		return false;
};

const debouncedBeforeUpload = debounce(async fileList => {
		...
		const res = await getQiniuTokenApi();
		const uploadPromises = fileList.map(async (file: any) => {
				return new Promise((resolve, reject) => {
					const formData = new FormData();
					formData.append("file", file);
					formData.append("token", res.data?.upToken || "");

					fetch("https://upload.qiniup.com/", {
						method: "POST",
						body: formData
					})
						.then(response => {
							if (response.status === 200) {
								return response
									.json()
									.then(data => {
										// 返回包含文件信息和响应数据的对象
										resolve({
											uid: file.uid,
											url: "https://" + res.data?.domain + "/" + data.key + "?attname=" + file.name,
											filePreviewUrl: "https://" + res.data?.domain + "/" + data.key
										});
									})
									.catch(() => {
										reject(new Error("Upload failed"));
									});
							} else {
								reject(new Error("Upload failed"));
							}
						})
						.catch(error => reject(error));
				});
		});
		Promise.all(uploadPromises)
				.then(res => {
					setPromiseAllResult(res);
					message.success("文件上传成功");
				})
				.catch(() => message.error("文件上传失败"));
});

useEffect(() => {
		if (promiseAllResult && promiseAllResult.length > 0 && fileList && fileList.length > 0) {
				fileList.forEach(item => {
					const findResult = promiseAllResult.find(file => file.uid === item.uid);
					if (findResult) {
						// @ts-ignore
						item.filePreviewUrl = findResult.filePreviewUrl;
						item.url = findResult.url;
					}
				});
		}
}, [promiseAllResult]);

4.完整代码:

封装文件上传组件:

src/component/Upload/index.tsx:

复制代码
import { forwardRef, useImperativeHandle, useState, useEffect } from "react";
import { Upload, message } from "antd";
import { InboxOutlined } from "@ant-design/icons";
import type { UploadFile, UploadProps } from "antd";
import { getQiniuTokenApi } from "@/api/modules/assetManagement";
import { debounce } from "lodash";
const { Dragger } = Upload;
interface UploadComType {
	maxCount?: number;
	accept?: string[];
	size?: number;
	multiple?: boolean;
}
const UploadCom = forwardRef(
	(
		{
			maxCount = 3,
			accept = [
				".doc",
				".docx",
				".xml",
				"application/msword",
				"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
				"application/pdf",
				"image/png",
				"image/jpeg"
			],
			size = 2,
			multiple = true
		}: UploadComType,
		ref: any
	) => {
		const [fileList, setFileList] = useState<UploadFile[]>([]);
		const [promiseAllResult, setPromiseAllResult] = useState<UploadFile[]>([]);
		useEffect(() => {
			if (promiseAllResult && promiseAllResult.length > 0 && fileList && fileList.length > 0) {
				fileList.forEach(item => {
					const findResult = promiseAllResult.find(file => file.uid === item.uid);
					if (findResult) {
						// @ts-ignore
						item.filePreviewUrl = findResult.filePreviewUrl;
						item.url = findResult.url;
					}
				});
			}
		}, [promiseAllResult]);
		useImperativeHandle(ref, () => ({
			getFileList: () => {
				return fileList;
			},
			parentSetList: (list: UploadFile[]) => {
				setFileList(list);
			}
		}));
		const onRemove = (file: UploadFile) => {
			const list = fileList.filter(item => item.uid !== file.uid);
			setFileList(list);
		};
		const beforeUpload: UploadProps["beforeUpload"] = (file, fileList) => {
			debouncedBeforeUpload(fileList);
			return false;
		};
		const debouncedBeforeUpload = debounce(async fileList => {
			let newFileList = fileList.filter((file: any) => {
				// 上传中的文件不进行校验
				if (file.status === "uploading") return true;

				// 校验文件类型
				const isFileTypeValid = accept.includes(file.type || "");
				if (!isFileTypeValid) {
					message.error(`${file.name} 不是允许的文件类型`);
					return false;
				}
				// 校验文件大小
				const isFileSizeValid = (file.size || 0) <= size * 1024 * 1024;
				if (!isFileSizeValid) {
					message.error(`${file.name} 超过最大文件大小限制 (${size}MB)`);
					return false;
				}
				return true;
			});
			if (newFileList.length === 0) {
				message.warning("没有符合要求的文件可上传");
				return false;
			}

			const res = await getQiniuTokenApi();
			const uploadPromises = newFileList.map(async (file: any) => {
				return new Promise((resolve, reject) => {
					const formData = new FormData();
					formData.append("file", file);
					formData.append("token", res.data?.upToken || "");

					fetch("https://upload.qiniup.com/", {
						method: "POST",
						body: formData
					})
						.then(response => {
							if (response.status === 200) {
								return response
									.json()
									.then(data => {
										// 返回包含文件信息和响应数据的对象
										resolve({
											name: file.name,
											size: file.size,
											uid: file.uid,
											type: file.type,
											status: file.status,
											url: "https://" + res.data?.domain + "/" + data.key + "?attname=" + file.name,
											filePreviewUrl: "https://" + res.data?.domain + "/" + data.key
										});
									})
									.catch(() => {
										reject(new Error("Upload failed"));
									});
							} else {
								reject(new Error("Upload failed"));
							}
						})
						.catch(error => reject(error));
				});
			});
			Promise.all(uploadPromises)
				.then(res => {
					setPromiseAllResult(res);
					message.success("文件上传成功");
				})
				.catch(() => message.error("文件上传失败"));
		});
		const handleChange: UploadProps["onChange"] = ({ fileList }) => {
			let newFileList = fileList;
			if (newFileList.length > maxCount) {
				message.warning(`最多可上传${maxCount}个文件`);
				newFileList = newFileList.slice(-maxCount);
			}
			setFileList(newFileList);
		};

		const uploadProps: UploadProps = {
			name: "file",
			onRemove,
			beforeUpload: beforeUpload,
			multiple: multiple,
			onChange: handleChange,
			accept: accept.join(",")
		};

		return (
			<div>
				<Dragger {...uploadProps} fileList={fileList}>
					<p className="ant-upload-drag-icon">
						<InboxOutlined />
					</p>
					<p className="ant-upload-text">Click or drag file to this area to upload</p>
					<p className="ant-upload-hint">
						Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.
					</p>
				</Dragger>
			</div>
		);
	}
);
export default UploadCom;
使用文件上传组件:
复制代码
import UploadCom from "@/components/Upload/index";

const uploadComRef = useRef<any>(null);

<UploadCom ref={uploadComRef} />

//获取组件中的文件
const file = uploadComRef.current?.getFileList();

//给组件中的文件赋初始值
uploadComRef.current?.parentSetList(files);
相关推荐
名字还没想好☜1 天前
Next.js 用 cookies()/headers() 读请求信息:动态渲染触发、缓存失效与在 Server Action 里读写 cookie
前端·javascript·缓存·react·next.js·app router
DsirNg1 天前
先守住数据,再下沉交互:RSC 组件分层的实战决策法
react·next.js·app router·前端架构·组件设计·react server components·server actions
递归尽头是星辰2 天前
大模型 Agent 知识体系:Java 开发者视角下的原理、架构与选型边界
react·智能体·spring ai·java 后端·大模型 agent
名字还没想好☜4 天前
Next.js Route Handler 做 SSE 服务端推送:实时进度条、自动重连与什么时候别用 WebSocket
开发语言·javascript·websocket·react·sse·next.js
Richown6 天前
AI + Web3 融合架构:大模型驱动的智能合约自动生成与审计
区块链·react
名字还没想好☜6 天前
React 19 useOptimistic 实战:点赞评论先更新 UI,失败自动回滚
前端·javascript·react.js·ui·react·useoptimistic
名字还没想好☜8 天前
Next.js 用 Server Components 直连数据库:去掉 API 层的边界,和三条别踩的安全红线
前端·javascript·数据库·安全·react·next.js
名字还没想好☜8 天前
React 用自定义 useDebounce Hook 治好搜索框频繁请求:防抖、竞态取消与卸载清理
前端·javascript·react.js·react·hooks
DsirNg8 天前
React Server Components 在真实项目中的边界:哪些组件该放在服务端
性能优化·react·next.js·app router·前端架构·rsc·react server components
名字还没想好☜9 天前
React 用 useEffect 做轮询实战:setInterval 拿到旧 state 的闭包陷阱与正确清理
前端·javascript·react.js·react·useeffect