You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React结合Material UI上传组件向API发送图片/文件失败的解决方案

问题描述

我使用Material UI的上传组件开发了一个图片上传组件。此前我仅使用HTML原生文件输入框实现过该功能,因此本次开发遇到了问题:文件可以正常选中并存入组件state,但当我将文件追加到FormData中发送到Node & Express服务端时,req.files始终返回空数组,服务端我使用multer解析上传的文件。

前端UI组件代码

const StepFive = ({
  handlePrevStep,
  setListingImages,
  listingImages,
  createdListing,
}) => {
  const created = useSelector((state) => state.listings.created);
  const [fileList, setFileList] = React.useState([]);
  const dispatch = useDispatch();

  React.useEffect(() => {
    setListingImages(fileList);
    console.log(fileList);
  }, [fileList]);

  const onChange = ({ fileList: newFileList }) => {
    setFileList(newFileList);
  };

  const handleLisitingImages = (e) => {
    e.preventDefault();

    const data = new FormData();

    // 校验是否选中了图片,将图片追加到FormData中
    if (fileList) {
      for (let i = 0; i < fileList.length; i++) {
        data.append('listingImage', fileList[i]);
      }
    }

    console.log(data.getAll('listingImage'));

    uploadImagesToListing(createdListing._id, data)(dispatch);
  };

  const onPreview = async (file) => {
    let src = file.url;
    if (!src) {
      src = await new Promise((resolve) => {
        const reader = new FileReader();
        reader.readAsDataURL(file.originFileObj);
        reader.onload = () => resolve(reader.result);
      });
    }
    const image = new Image();
    image.src = src;
    const imgWindow = window.open(src);
    imgWindow.document.write(image.outerHTML);
  };

  // const prevStep = (e) => {
  //   e.preventDefault();
  //   handlePrevStep();
  // };

  // if (!created) return <Spinner />;

  return (
    <Con>
      <Wrap>
        <Header>
          Step <b>5|5</b>
        </Header>
        <SubHeader>Pictures</SubHeader>
        <SubInfo>
          This is the most important part of creating a listing and attratcing
          guests. Really spend time on taking some good quality pictures of your
          place
        </SubInfo>
        <UploadForm enctype='multipart/form-data'>
          <ImgCrop rotate>
            <Upload
              listType='picture-card'
              fileList={fileList}
              onChange={onChange}
              onPreview={onPreview}
              name='image'
            >
              {fileList.length < 15 && '+ Upload'}
            </Upload>
          </ImgCrop>
        </UploadForm>
        <BtnCon>
          <Btn onClick={prevStep}>Prev</Btn>
          <Btn onClick={handleLisitingImages}>Finish</Btn>
        </BtnCon>
      </Wrap>
    </Con>
  );
};

上传请求封装代码

export const uploadImagesToListing = (listingId, images) => async (dispatch) => {
  await axiosInstance
    .post(`/listings/imageupload/${listingId}`, images, {
      headers: { 'Content-Type': `multipart/form-data;` },
    })
    .then((res) => {
      dispatch({
        type: uploadedImages,
      });
    })
    .catch((err) => {
      console.log(err);
    });
};

服务端multer配置代码

const storage = multer.memoryStorage();

// 配置多文件上传,对应字段名为listingImage
const multipleUpload = multer({ storage: storage }).array('listingImage');

router.post(
  '/imageupload/:listingId',
  verifyToken,
  multipleUpload,
  listings.imageUpload
);

问题表现

在路由处理函数中打印req.files,始终返回空数组。

解决方案

Material UI Upload组件返回的fileList每一项是组件封装后的自定义对象,真实的原生File对象存储在originFileObj属性下。之前直接把封装后的对象追加到FormData中,multer无法识别为合法的文件对象,因此解析结果为空。
仅需要修改前端追加FormData的逻辑即可:

if (fileList) {
  for (let i = 0; i < fileList.length; i++) {
    // 取封装对象内的原生File对象传入
    data.append('listingImage', fileList[i].originFileObj);
  }
}

额外优化建议

使用axios发送FormData请求时,不需要手动设置Content-Type: multipart/form-data请求头,浏览器会自动生成带分隔符(boundary)的请求头,手动设置反而可能因为缺少boundary参数导致解析失败。


内容的提问来源于stack exchange,提问作者Stinny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 04:15:03