使用 React 上传和处理图像

Avatar of Damon Bauer
Damon Bauer 发表

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

以下是 Damon Bauer 的客座文章,他解决了 Web 开发人员的一个常见任务:提供用户图像上传功能。我不敢说这很容易,但借助一些强大的工具来完成大部分繁重工作,这项任务比以前容易多了。Damon 甚至在浏览器中 完成 了所有操作!

Web 开发人员需要做的常见事情之一是允许用户上传图像。起初这似乎微不足道,但在构建图像上传组件时需要考虑一些事项。以下只是一些需要考虑的事项

  • 您将允许哪些图像类型?
  • 图像需要多大?这将如何影响性能?
  • 图像的纵横比应是多少?
  • 图像将如何进行审核?如何捕捉不合适的图像?
  • 图像将存储在哪里?如何管理?

服务器端工具(例如 PaperclipImageProcessor)为大多数这些问题提供了解决方案。不幸的是,在单页应用程序中没有现成的工具可以使用(我还没有找到)。我将向您展示如何在不使用任何服务器端语言的 React 应用程序中解决此问题。

这是一个我们将要构建的内容的演示

工具包

我使用的三个工具包括

设置 Cloudinary

Cloudinary 是一种基于云的服务,您可以在其中存储、处理、管理和提供图像。我选择使用 Cloudinary,因为它有一个免费层,其中包含我需要的所有功能。您至少需要一个免费帐户才能开始使用。

假设您想裁剪、调整大小并为上传的图像添加滤镜。Cloudinary 有“转换”的概念,这些转换链接在一起以根据需要修改图像。上传后,将进行转换,修改并存储新图像。

在 Cloudinary 仪表板中,转到设置 > 上传,并在上传预设下选择“添加上传预设”。

在以下屏幕上,将“模式”更改为“未签名”。这对于无需使用服务器端语言协商私钥即可直接上传到 Cloudinary 是必需的。

通过在“传入转换”部分中选择“编辑”来添加任何转换。您可以在此处裁剪、调整大小、更改质量、旋转、过滤等。保存预设,就是这样!您现在有了一个可以为您的应用程序上传、处理、存储和提供图像的地方。请注意预设名称,因为我们稍后会使用它。让我们继续进行代码。

接收用户输入

为了处理图像上传,我使用了 react-dropzone。它包括拖放、文件类型限制和多文件上传等功能。

首先,安装依赖项。在命令行中,运行

npm install react react-dropzone superagent --save

然后将Reactreact-dropzonesuperagent导入到您的组件中。我正在使用 ES6 import 语法

import React from 'react';
import Dropzone from 'react-dropzone';
import request from 'superagent';

我们稍后会使用superagent。现在,在组件的 render 方法中,包含一个react-dropzone实例

export default class ContactForm extends React.Component {

  render() {
    <Dropzone
      multiple={false}
      accept="image/*"
      onDrop={this.onImageDrop.bind(this)}>
      <p>Drop an image or click to select a file to upload.</p>
    </Dropzone>
  }

Readyer Lucas Recoaro 在评论中提到以下 Dropzone 代码片段对他来说效果更好。似乎库的新版本中的语法发生了变化。

<Dropzone
  onDrop={this.onImageDrop.bind(this)}
  accept="image/*"
  multiple={false}>
    {({getRootProps, getInputProps}) => {
      return (
        <div
          {...getRootProps()}
        >
          <input {...getInputProps()} />
          {
          <p>Try dropping some files here, or click to select files to upload.</p>
          }
        </div>
      )
  }}
</Dropzone>

以下是此组件正在执行的操作概述

  • multiple={false} 允许一次只上传一个图像。
  • accept="image/*" 允许任何图像类型。您可以更明确地限制某些文件类型,例如accept="image/jpg,image/png"
  • onDrop 是在上传图像时触发的函数。

当使用 React ES5 类语法(React.createClass)时,所有函数都会“自动绑定”到类实例。本文中的代码使用 ES6 类语法(extends React.Component),它不提供自动绑定。这就是我们在onDrop prop 中使用.bind(this) 的原因。(如果您不熟悉.bind,可以在这里阅读相关信息。)

处理图像拖放

现在,让我们设置一个函数,以便在上传图像时执行某些操作。

首先,为两条重要的上传信息设置一个const

  1. 上传预设 ID(在您创建上传预设时自动为您创建)
  2. 您的 Cloudinary 上传 URL
// import statements

const CLOUDINARY_UPLOAD_PRESET = 'your_upload_preset_id';
const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/your_cloudinary_app_name/upload';

export default class ContactForm extends React.Component {
// render()

接下来,在组件的初始状态中添加一个条目(使用this.setState);我将其称为uploadedFileCloudinaryUrl。最终,它将保存由 Cloudinary 创建的上传图像 URL。我们稍后会用到这个状态值。

export default class ContactForm extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      uploadedFileCloudinaryUrl: ''
    };
  }

react-dropzone 文档指出它始终会返回上传文件数组,因此我们将该数组传递给onImageDrop函数的files参数。由于我们一次只允许一个图像,因此我们知道该图像始终位于数组的第一个位置。

调用handleImageUpload,将图像(files[0])传递给此函数。我将其分解为一个单独的函数,遵循单一职责原则。从本质上讲,此原则教您使函数保持简洁,并且只做一件事。

export default class ContactForm extends React.Component {

  constructor(props) { ... }

  onImageDrop(files) {
    this.setState({
      uploadedFile: files[0]
    });

    this.handleImageUpload(files[0]);
  }

  render() { ... }

}

处理图像上传和传输

首先,使用superagent通过我们之前设置的两个const将数据 POST 到 Cloudinary。使用.field函数,我们可以将数据附加到 POST 请求中。这些数据包含 Cloudinary 处理上传图像所需的所有信息。通过调用.end,将执行请求并提供回调函数。

export default class ContactForm extends React.Component {

  constructor(props) { ... }

  onImageDrop(files) { ... }

  handleImageUpload(file) {
    let upload = request.post(CLOUDINARY_UPLOAD_URL)
                        .field('upload_preset', CLOUDINARY_UPLOAD_PRESET)
                        .field('file', file);

    upload.end((err, response) => {
      if (err) {
        console.error(err);
      }

      if (response.body.secure_url !== '') {
        this.setState({
          uploadedFileCloudinaryUrl: response.body.secure_url
        });
      }
    });
  }

  render() { ... }

}

.end回调函数内部,我记录了返回的任何错误。最好也告诉用户发生了错误。

接下来,我们检查收到的响应中是否包含非空字符串的 URL。这意味着图像已上传和处理,并且 Cloudinary 生成了一个 URL。例如,如果用户正在编辑其个人资料并上传了图像,则可以将 Cloudinary 中的新图像 URL 存储到您的数据库中。

通过我们目前编写的代码,用户可以拖放图像,组件会将其发送到 Cloudinary,并接收转换后的图像 URL 供我们使用。

渲染,续

组件的最后一部分是一个div,它包含上传图像的预览。

export default class ContactForm extends React.Component {

  constructor(props) { ... }

  onImageDrop(files) { ... }

  handleImageUpload(file) { ... }

  render() {
    <div>
      <div className="FileUpload">
        ...
      </div>

      <div>
        {this.state.uploadedFileCloudinaryUrl === '' ? null :
        <div>
          <p>{this.state.uploadedFile.name}</p>
          <img src={this.state.uploadedFileCloudinaryUrl} />
        </div>}
      </div>
    </div>
  }

三元运算符在uploadedFileCloudinaryUrl状态为空字符串时输出null(无)。回想一下,默认情况下,我们将组件的uploadedFileCloudinaryUrl状态设置为一个空字符串;这意味着当组件渲染时,此div将为空。

但是,当 Cloudinary 返回一个 URL 时,状态不再为空字符串,因为我们在handleImageUpload中更新了状态。此时,组件将重新渲染,显示上传文件的名称和转换后的图像预览。

总结

这仅仅是一个图像上传组件的基础框架。您可以添加许多其他功能,例如

  • 允许上传多张图片
  • 删除已上传的图片
  • 如果上传由于任何原因失败,则显示错误
  • 使用移动设备的摄像头作为上传源

到目前为止,此设置已很好地满足我的需求。不得不硬编码上传预设并不完美,但我还没有遇到任何问题。

希望您已经了解了如何在不使用服务器端语言的情况下使用 React 上传、存储和处理图像。如果您有任何问题或意见,我非常乐意听取!我创建了一个存储库,您可以在其中查看此代码的实际效果