以下是 Damon Bauer 的客座文章,他解决了 Web 开发人员的一个常见任务:提供用户图像上传功能。我不敢说这很容易,但借助一些强大的工具来完成大部分繁重工作,这项任务比以前容易多了。Damon 甚至在浏览器中 完成 了所有操作!
Web 开发人员需要做的常见事情之一是允许用户上传图像。起初这似乎微不足道,但在构建图像上传组件时需要考虑一些事项。以下只是一些需要考虑的事项
- 您将允许哪些图像类型?
- 图像需要多大?这将如何影响性能?
- 图像的纵横比应是多少?
- 图像将如何进行审核?如何捕捉不合适的图像?
- 图像将存储在哪里?如何管理?
服务器端工具(例如 Paperclip 和 ImageProcessor)为大多数这些问题提供了解决方案。不幸的是,在单页应用程序中没有现成的工具可以使用(我还没有找到)。我将向您展示如何在不使用任何服务器端语言的 React 应用程序中解决此问题。
这是一个我们将要构建的内容的演示

工具包
我使用的三个工具包括
- react-dropzone 用于接收用户的图像
- superagent 用于传输上传的图像
- Cloudinary 用于存储和处理图像
设置 Cloudinary
Cloudinary 是一种基于云的服务,您可以在其中存储、处理、管理和提供图像。我选择使用 Cloudinary,因为它有一个免费层,其中包含我需要的所有功能。您至少需要一个免费帐户才能开始使用。
假设您想裁剪、调整大小并为上传的图像添加滤镜。Cloudinary 有“转换”的概念,这些转换链接在一起以根据需要修改图像。上传后,将进行转换,修改并存储新图像。
在 Cloudinary 仪表板中,转到设置 > 上传,并在上传预设下选择“添加上传预设”。

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

通过在“传入转换”部分中选择“编辑”来添加任何转换。您可以在此处裁剪、调整大小、更改质量、旋转、过滤等。保存预设,就是这样!您现在有了一个可以为您的应用程序上传、处理、存储和提供图像的地方。请注意预设名称,因为我们稍后会使用它。让我们继续进行代码。
接收用户输入
为了处理图像上传,我使用了 react-dropzone。它包括拖放、文件类型限制和多文件上传等功能。
首先,安装依赖项。在命令行中,运行
npm install react react-dropzone superagent --save
然后将React
、react-dropzone
和superagent
导入到您的组件中。我正在使用 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
- 上传预设 ID(在您创建上传预设时自动为您创建)
- 您的 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 上传、存储和处理图像。如果您有任何问题或意见,我非常乐意听取!我创建了一个存储库,您可以在其中查看此代码的实际效果。
这种方法是否适用于视频文件?我之所以这么问是因为 Cloudinary 支持视频。
Ali - 你应该能够用视频实现同样的效果。
react-dropzone
不关心文件类型,就像你说的,Cloudinary 支持视频。如果你成功了,回复我!很棒的东西!Damon,如果你发布到 Github 上,你可能需要更改你的 Cloudinary 预设,因为它可能仍然有效。
你找到处理不当内容的审核服务了吗?
Kevin - Cloudinary 有你可以付费使用的附加组件,可以帮助自动化图像审核。这里有一个包含更多信息的链接:http://cloudinary.com/documentation/webpurify_image_moderation_addon
总体来说这是一篇不错的文章,但为什么你不从头开始构建文件上传呢?我们中的更多人可以将 Dropzone 放入项目中,但实际上很少有人能够在 React 中编写文件上传器。
你不应该在
render
方法中绑定回调,因为这会在每次状态更改时产生一个新函数。相反,应该在构造函数中只绑定一次。我按照说明操作,也查看了你的演示代码。
为什么我会收到来自“Request._getFormData”的错误“this._formData.on 不是函数”?
我也是……
很棒的文章。正是我想要的。