平时把<Upload/>这个组件拿过来直接用,但是实现原理不是很明确,自己记录一下。
Ant Design Upload 组件基于原生 <input type="file">实现,
<Upload
name="file" // 发给后台时,文件对应的字段名
action="/api/upload" // 文件上传的目标地址
headers={{ Authorization: 'Bearer token' }} // 设置请求头,常用于身份验证
data={{ extra: 'data' }} // 除了文件,额外带给后台的参数
multiple={true} // 允许一次选择多个文件
accept=".jpg,.png" // 限制只能选择 JPG 和 PNG 图片
beforeUpload={beforeUpload} // 上传前的“安检”函数,可校验文件
onChange={handleChange} // 上传状态(进行中/完成/失败)变化时的回调
fileList={fileList} // 控制显示已上传文件的列表
>
<Button>点击上传</Button>
</Upload>
上传流程拆解(比喻:快递寄件)
你可以把整个上传过程想象成去快递站寄包裹:
一、准备阶段(初始化)
组件初始化时,就像快递站开门营业:
- 准备好一个“快递柜”(隐藏的
<input type=“file”>)。 - 在柜子上贴好告示:只收衣服和书籍(
accept),可以同时寄多个(multiple)。 - 安排好一位“接待员”(change事件监听器),专门等你挑选好物品。
- 如果已有包裹在途中(
fileList),就把它们的物流信息展示出来。
二、挑选物品(选择文件)
你点击“上传”按钮,就像按下快递柜的“寄件”按钮,会弹出文件选择窗口。你选择文件后,“接待员”立刻收到通知,开始处理你选的“物品”。
三、打包与寄出(上传请求)
“接待员”不会直接把你的物品寄走,而是会先打包,并发起一个网络请求。这个过程类似于:
// 1. 准备快递箱 (FormData)
const formData = new FormData();
formData.append('file', yourFile); // 把文件放进箱子
formData.append('extra', 'data'); // 放进填充物(额外数据)
// 2. 填写快递单 (配置请求)
const xhr = new XMLHttpRequest();
xhr.open(‘POST’, ‘/api/upload’, true);
xhr.setRequestHeader(‘Authorization’, ‘Bearer token’); // 贴上保价单(请求头)
// 3. 监听运输进度
xhr.upload.onprogress = (e) => {
const percent = Math.round((e.loaded / e.total) * 100); // 计算已运输百分比
console.log(`当前进度: ${percent}%`);
};
// 4. 发出包裹
xhr.send(formData);
核心“安检”环节:beforeUpload
在文件真正发出前,beforeUpload函数就像一道安检程序,可以进行各种检查:
const beforeUpload = (file) => {
// 1. 检查“物品类型”:是不是图片?
const isImage = file.type === ‘image/jpeg’ || file.type === ‘image/png’;
if (!isImage) {
message.error(‘只能上传 JPG 或 PNG 图片!’);
return false; // 安检不通过,拒绝寄出
}
// 2. 检查“物品大小”:是否超过2MB?
const isWithinSizeLimit = file.size / 1024 / 1024 < 2;
if (!isWithinSizeLimit) {
message.error(‘文件太大了,不能超过2MB!’);
return false;
}
// 3. 更细致的检查(例如:检查图片尺寸):可以返回一个 Promise
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
const img = new Image();
img.src = e.target.result;
img.onload = () => {
// 如果图片宽高都大于300像素,才允许上传
if (img.width > 300 && img.height > 300) {
resolve(true); // 安检通过
} else {
reject(new Error(‘图片尺寸太小了’)); // 安检不通过
}
};
};
});
};
使用案例:头像上传组件
下面是一个结合了上述原理的实战案例——用户头像上传:
import { Upload, Button, message } from ‘antd’;
import { UploadOutlined } from ‘@ant-design/icons’;
import { useState } from ‘react’;
const AvatarUploader = () => {
const [fileList, setFileList] = useState([]);
const [uploading, setUploading] = useState(false);
// 自定义上传前校验
const beforeUpload = (file) => {
const isJpgOrPng = file.type === ‘image/jpeg’ || file.type === ‘image/png’;
if (!isJpgOrPng) {
message.error(‘请上传 JPG/PNG 格式的图片!’);
return false;
}
const isLt1M = file.size / 1024 / 1024 < 1;
if (!isLt1M) {
message.error(‘图片大小不能超过 1MB!’);
return false;
}
// 通过校验,允许上传
return true;
};
// 处理上传状态变化
const handleChange = (info) => {
let newFileList = [...info.fileList];
// 通常这里只显示最后一个文件(用于头像)
newFileList = newFileList.slice(-1);
setFileList(newFileList);
if (info.file.status === ‘uploading’) {
setUploading(true);
}
if (info.file.status === ‘done’) {
message.success(`${info.file.name} 上传成功`);
setUploading(false);
// 通常这里会触发父组件更新头像URL: onSuccess(info.file.response.url);
} else if (info.file.status === ‘error’) {
message.error(`${info.file.name} 上传失败`);
setUploading(false);
}
};
return (
<Upload
name=“avatar”
action=“/api/upload/avatar”
listType=“picture”
fileList={fileList}
beforeUpload={beforeUpload}
onChange={handleChange}
maxCount={1} // 限制只能上传一个文件
showUploadList={{ showPreviewIcon: false }} // 隐藏预览图标
>
<Button icon={<UploadOutlined />} loading={uploading} disabled={uploading}>
{uploading ? ‘上传中…’ : ‘更换头像’}
</Button>
</Upload>
);
};