使用 React + Node.js 实现图片上传功能!

写在前面

最近笔者在开发个人博客的后台管理系统,里面用到了图片上传相关的功能,在这里记录并分享一下,希望可以帮到大家,话不多说直接开始吧!(只想看代码的可以直接跳到最后哦~)

效果

技术栈

前端:react + antdesign

后端:node + express + multer + uuid

前端

上传前

这边前端直接使用的 antdesign 所提供的上传组件,代码如下

HTML 复制代码
       <Upload
              accept="multipart/form-data"
              name="avater"
              listType="picture-card"
              fileList={[]} // 手动设置空的 fileList
              className="avatar-uploader"
              showUploadList={false}
              beforeUpload={(file) => {
                handlePreview(file)
                return false // 阻止默认上传行为
              }}
            >
              {imageUrl ? <img src={imageUrl} alt="avater" style={{ width: '100%' }} /> : uploadButton}
            </Upload>

accept="multipart/form-data: 是用于指定在 HTML 表单中上传文件时使用的编码类型。它告诉服务器将表单数据编码为 multipart/form-data 格式,这是用于上传文件的一种常见方式。

beforeUpload: 则是需要在上传前对文件进行处理,我们来看一下代码里面发生了什么。

JS 复制代码
  const handlePreview = async (file: File) => {
    setImageUrl('')
    setLoading(true)
    const isImage = file.type.startsWith('image/')
    const isImageExtension = /.(jpg|jpeg|png|gif)$/i.test(file.name)

    if (!isImage || !isImageExtension) {
      message.error(`${file.name} is not a supported image file`)
      return Upload.LIST_IGNORE
    }

    try {
      const reader = new FileReader()
      reader.onload = () => {
        setImageUrl(reader.result as string)
        setLoading(false)
      }
      reader.readAsDataURL(file)
    } catch (error) {
      console.error('图片预览失败', error)
      message.error('图片预览失败')
      setLoading(false)
    }
  }

首先判断文件的格式是不是图片类型的,如果不是,则终止上传。

JS 复制代码
    const isImage = file.type.startsWith('image/')
    const isImageExtension = /.(jpg|jpeg|png|gif)$/i.test(file.name)

    if (!isImage || !isImageExtension) {
      message.error(`${file.name} is not a supported image file`)
      return Upload.LIST_IGNORE
    }

如果是图片类型则创建一个新的FileReader对象。

reader.readAsDataURL(file) :开始读取指定的文件,这里的 file 是一个文件对象,通过某种方式传递给这段代码。readAsDataURL方法会将文件内容读取为Data URL。

reader.onload:设置一个事件处理程序,当文件读取完成时会触发该事件。

在读取完成时,触发 onload 事件。在这个函数中,通过 reader.result 获取读取到的Data URL,然后使用 setImageUrl 函数将其反显到页面当中。

JS 复制代码
    const reader = new FileReader()
      reader.onload = () => {
        setImageUrl(reader.result as string)
        setLoading(false)
      }
      reader.readAsDataURL(file)

这段上传前的代码是为了判断是否为图片类型,以及读取出来图片反显到页面当中,大家根据自己的需求调整。

反显效果如下:

上传

上传前准备工作以就绪,接下来就是点击上传,注意我这里因为有其他属性,需要一起上传。

如果只是单独的上传图片一个功能,antdesign 提供的有现成的代码,只需要在action属性写上后端地址即可。

点击上传

我们使用 antdesign 的表单提交功能,点击上传后得到一个 values 对象如下:

我们的图片信息在 pic 字段下,这个字段是自己设置的,下面是点击上传文件相关代码。

JS 复制代码
  const handleAddRequest = async (values: any) => {
    const formData = new FormData()
    if (values.pic && values.pic.fileList.length) {
      const file = values.pic.fileList[0].originFileObj
      formData.append('file', file, values.pic.fileList[0].name)
    }

    fetch(baseUrl + '/add-article', {
      method: 'POST',
      body: formData,
    })
      .then((res) => res.json())
      .then((res) => {
      })
      .catch((error) => {
      })
  }

首先创建一个新的 FormData 对象,用于构建表单数据。

JS 复制代码
const formData = new FormData()

检查是否存在图片信息(values.pic),并且图片文件列表(values.pic.fileList)不为空。

values.pic.fileList[0].originFileObj 则是获取这个文件对象的原始文件对象。

将文件添加到 FormData

JS 复制代码
   const file = values.pic.fileList[0].originFileObj
   formData.append('file', file, values.pic.fileList[0].name)

注意 'file' 需要和后端读取文件的命名一致,等会后端会介绍。

最后只需要将整理好的 formDat a对象放到 body 中发给后端即可!

后端

首先安装相关模块

JS 复制代码
npm i express //用于接口请求
npm i multer  //文件模块
npm i uuid    //文件命名

指定静态页面(图片存储文件夹)

JS 复制代码
app.use('/uploads/', express.static('./uploads')) //指定静态页面

解析 Body 数据

JS 复制代码
const bodyParser = require('body-parser')
app.use(bodyParser.json())

配置 multer 磁盘存储

JS 复制代码
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')//图片要存储的文件夹地址
  },
  filename: function (req, file, cb) {
    cb(null, uuid.v1() + '.' + file.originalname.split('.').pop())//存储的文件名
  },
})

使用配置好的存储创建 multer 实例

JS 复制代码
const upload = multer({ storage: storage })

定义一个用于添加文章的路由,支持文件上传,使用 upload.single('file') 作为中间件来处理带有字段名 'file' 的单个文件上传,这与上面前端上传的命名相对应( formData.append('file', file, values.pic.fileList[0].name))

JS 复制代码
app.post('/add-article', upload.single('file'), async (req, res) => {
  const filePath = 'uploads/' + req.file.filename // 获取上传文件存储的文件路径
  //todo.....
})

最后上传的图片会存到 uploads 文件夹中

完整代码

由于整个项目不单单是这一个功能,这里就把上传图片功能所用到的前后端代码进行分享,有需要的同学可以参考一下代码!

前端

HTML 复制代码
HTML      
 <Upload
              accept="multipart/form-data"
              name="avater"
              listType="picture-card"
              fileList={[]} // 手动设置空的 fileList
              className="avatar-uploader"
              showUploadList={false}
              beforeUpload={(file) => {
                handlePreview(file)
                return false // 阻止默认上传行为
              }}
            >
              {imageUrl ? <img src={imageUrl} alt="avater" style={{ width: '100%' }} /> : uploadButton}
            </Upload>

JS
##上传前操作
  const handlePreview = async (file: File) => {
    setImageUrl('')
    setLoading(true)
    const isImage = file.type.startsWith('image/')
    const isImageExtension = /.(jpg|jpeg|png|gif)$/i.test(file.name)

    if (!isImage || !isImageExtension) {
      message.error(`${file.name} is not a supported image file`)
      return Upload.LIST_IGNORE
    }

    try {
      const reader = new FileReader()
      reader.onload = () => {
        setImageUrl(reader.result as string)
        setLoading(false)
      }
      reader.readAsDataURL(file)
    } catch (error) {
      console.error('图片预览失败', error)
      message.error('图片预览失败')
      setLoading(false)
    }
  }

##上传处理字段
  const handleAddRequest = async (values: any) => {
    const formData = new FormData()
    if (values.pic && values.pic.fileList.length) {
      const file = values.pic.fileList[0].originFileObj
      formData.append('file', file, values.pic.fileList[0].name)
    }

    fetch(baseUrl + '/add-article', {
      method: 'POST',
      body: formData,
    })
      .then((res) => res.json())
      .then((res) => {
      })
      .catch((error) => {
      })
  }

后端

JS 复制代码
const express = require('express')
const multer = require('multer') //文件上传模块
const uuid = require('uuid') //生成随机uuid-图片命名需要
app.use('/uploads/', express.static('./uploads')) //指定静态页面
//解析Body数据
const bodyParser = require('body-parser')
app.use(bodyParser.json())


const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')
  },
  filename: function (req, file, cb) {
    cb(null, uuid.v1() + '.' + file.originalname.split('.').pop())
  },
})

const upload = multer({ storage: storage })

// 新增文章
app.post('/add-article', upload.single('file'), async (req, res) => {
  const filePath = 'uploads/' + req.file.filename//存储的文件名
  //todo.....
})
相关推荐
恋猫de小郭5 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端