antd组件Upload的实现原理

43 阅读2分钟

平时把<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>

上传流程拆解(比喻:快递寄件)

你可以把整个上传过程想象成去快递站寄包裹

一、准备阶段(初始化)

组件初始化时,就像快递站开门营业:

  1. 准备好一个“快递柜”(隐藏的 <input type=“file”>)。
  2. 在柜子上贴好告示:只收衣服和书籍(accept),可以同时寄多个(multiple)。
  3. 安排好一位“接待员”(change事件监听器),专门等你挑选好物品。
  4. 如果已有包裹在途中(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(‘只能上传 JPGPNG 图片!’);
    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 { UploadButton, 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>
  );
};