Home Web Front-end JS Tutorial Share an example tutorial on the implementation principle of the image upload component (React + Node)

Share an example tutorial on the implementation principle of the image upload component (React + Node)

May 11, 2017 pm 01:37 PM
react

This article mainly introduces the Node-based ReactPictureUpload component implementation example code, which is of great practical value. Friends in need can refer to it

Written in front

If the red flag does not fall, I vow to carry out JavaScript to the end! Today I will introduce the front-end and back-end implementation principles (React + Node) of the image upload component in my open source project Royal. It took me some time and I hope it will be helpful to you.

Front-end implementation

Following the idea of ​​React componentization, I made the image upload an independent component (no other dependency), just import it directly.

1

2

3

4

5

6

7

8

9

10

import React, { Component } from 'react'

import Upload from '../../components/FormControls/Upload/'

 

//......

 

render() {

  return (

    <p><Upload uri={&#39;http://jafeney.com:9999/upload&#39;} /></p>

  )

}

Copy after login

The uri parameter must be passed, which is the backend interface address for image upload. How to write the interface will be discussed below.

Rendering page

The render part of the component needs to reflect three functions:

  1. Image selection (dialog window)

  2. Drag-able function (drag-and-drop container)

  3. Preview-able (preview list)

  4. UploadButton (button)

  5. Upload completed image address and link (information list)

Main renderFunction

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

render() {

  return (

    <form action={this.state.uri} method="post" encType="multipart/form-data">

      <p className="ry-upload-box">

        <p className="upload-main">

          <p className="upload-choose">

            <input

              onChange={(v)=>this.handleChange(v)}

              type="file"

              size={this.state.size}

              name="fileSelect"

              accept="image/*"

              multiple={this.state.multiple} />

            <span ref="dragBox"

              onDragOver={(e)=>this.handleDragHover(e)}

              onDragLeave={(e)=>this.handleDragHover(e)}

              onDrop={(e)=>this.handleDrop(e)}

              className="upload-drag-area">

              或者将图片拖到此处

            </span>

          </p>

          <p className={this.state.files.length?

             "upload-preview":"upload-preview ry-hidden"}>

            { this._renderPreview();  // 渲染图片预览列表 }

          </p>

        </p>

        <p className={this.state.files.length?

           "upload-submit":"upload-submit ry-hidden"}>

           <button type="button"

             onClick={()=>this.handleUpload()}

             class="upload-submit-btn">

             确认上传图片

           </button>

        </p>

        <p className="upload-info">

          { this._renderUploadInfos();  // 渲染图片上传信息 }

        </p>

      </p>

    </form>

  )

}

Copy after login

Rendered image preview list

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

_renderPreview() {

  if (this.state.files) {

    return this.state.files.map((item, idx) => {

      return (

        <p className="upload-append-list">

          <p>

            <strong>{item.name}</strong>

            <a href="javascript:void(0)" rel="external nofollow"

              className="upload-delete"

              title="删除" index={idx}></a>

            <br/>

            <img src={item.thumb} className="upload-image" />

           </p>

           <span className={this.state.progress[idx]?

             "upload-progress":

             "upload-progress ry-hidden"}>

             {this.state.progress[idx]}

           </span>

         </p>

      )

    })

  } else {

    return null

  }

}

Copy after login

Rendered image upload information list

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

_renderUploadInfos() {

  if (this.state.uploadHistory) {

    return this.state.uploadHistory.map((item, idx) => {

      return (

        <p>

          <span>上传成功,图片地址是:</span>

          <input type="text" class="upload-url" value={item.relPath}/>

          <a href={item.relPath} target="_blank">查看</a>

         </p>

      );

    })

  } else {

    return null;

  }

}

Copy after login

File upload

The principle of uploading images on the front end is to construct the FormData object, append() the file object to the object, and then mount it on the XMLHttpRequest object send( ) to the server.

Get the file object

To get the file object, you need to use the change event of the input input box to get the handle parameter e

1

onChange={(e)=>this.handleChange(e)}

Copy after login

Then Do the following processing:

1

2

3

4

5

6

7

8

9

10

11

12

13

e.preventDefault()

let target = event.target

let files = target.files

let count = this.state.multiple ? files.length : 1

for (let i = 0; i < count; i++) {

  files[i].thumb = URL.createObjectURL(files[i])

}

// 转换为真正的数组

files = Array.prototype.slice.call(files, 0)

// 过滤非图片类型的文件

files = files.filter(function (file) {

  return /image/i.test(file.type)

})

Copy after login

At this time, files is an array of file objects we need, and concat it into the original files.

1

this.setState({files: this.state.files.concat(files)})

Copy after login

In this way, the next operation can get the currently selected image file through this.state.files.

Use Promise to handle asynchronous upload

File upload is asynchronous to the browser. In order to handle the subsequent multi-image upload, Promise is introduced here to handle asynchronous Operation:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

upload(file, idx) {

  return new Promise((resolve, reject) => {

    let xhr = new XMLHttpRequest()

    if (xhr.upload) {

      // 上传中

      xhr.upload.addEventListener("progress", (e) => {

        // 处理上传进度

        this.handleProgress(file, e.loaded, e.total, idx);

      }, false)

      // 文件上传成功或是失败

      xhr.onreadystatechange = (e) => {

        if (xhr.readyState === 4) {

          if (xhr.status === 200) {

          // 上传成功操作

          this.handleSuccess(file, xhr.responseText)

          // 把该文件从上传队列中删除

          this.handleDeleteFile(file)

          resolve(xhr.responseText);

         } else {

          // 上传出错处理

          this.handleFailure(file, xhr.responseText)

          reject(xhr.responseText);

         }

      }

    }

    // 开始上传

    xhr.open("POST", this.state.uri, true)

    let form = new FormData()

    form.append("filedata", file)

    xhr.send(form)

  })

}

Copy after login

Upload progress calculation

The advantage of using the XMLHttpRequest object to send asynchronous requests is that it can calculate the progress of request processing, which fetch does not have.

We can add event monitoring for the progress event of the xhr.upload object:

1

2

3

4

xhr.upload.addEventListener("progress", (e) => {

  // 处理上传进度

  this.handleProgress(file, e.loaded, e.total, i);

}, false)

Copy after login

Description: The idx parameter is the index that records the multi-image upload queue

1

2

3

4

5

6

handleProgress(file, loaded, total, idx) {

  let percent = (loaded / total * 100).toFixed(2) + &#39;%&#39;;

  let _progress = this.state.progress;

  _progress[idx] = percent;

  this.setState({ progress: _progress }) // 反馈到DOM里显示

}

Copy after login

Drag-and-drop upload

Drag-and-drop files are actually very simple for HTML5, because it comes with several event listening mechanisms that can be done directly This type of processing. The following three are mainly used:

1

2

3

onDragOver={(e)=>this.handleDragHover(e)}

onDragLeave={(e)=>this.handleDragHover(e)}

onDrop={(e)=>this.handleDrop(e)}

Copy after login

Browser behavior when canceling drag and drop:

1

2

3

4

handleDragHover(e) {

  e.stopPropagation()

  e.preventDefault()

}

Copy after login

Processing of dragged in files:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

handleDrop(e) {

  this.setState({progress:[]})

  this.handleDragHover(e)

  // 获取文件列表对象

  let files = e.target.files || e.dataTransfer.files

  let count = this.state.multiple ? files.length : 1

  for (let i = 0; i < count; i++) {

    files[i].thumb = URL.createObjectURL(files[i])

  }

  // 转换为真正的数组

  files = Array.prototype.slice.call(files, 0)

  // 过滤非图片类型的文件

  files = files.filter(function (file) {

    return /image/i.test(file.type)

  })

  this.setState({files: this.state.files.concat(files)})

}

Copy after login

More Pictures are uploaded at the same time

To support multiple picture uploads, we need to set the property at the component call:

1

2

multiple = { true } // 开启多图上传

size = { 50 }    // 一次最大上传数量(虽没有上限,为保证服务端正常,建议50以下)

Copy after login

Then we can use Promise.all() to handle asynchronous operations Queue:

1

2

3

4

5

6

7

handleUpload() {

  let _promises = this.state.files.map((file, idx) => this.upload(file, idx))

  Promise.all(_promises).then( (res) => {

    // 全部上传完成

    this.handleComplete()

  }).catch( (err) => { console.log(err) })

}

Copy after login

Okay, the front-end work has been completed, and the next step is the work of Node.

Backend implementation

For convenience, the backend uses expressframework to quickly build Http services and routing. For specific projects, see my github node-image-upload. Although the logic is simple, there are still several notable points:

Cross-domain call

The backend of this project uses express, we can use res .header() sets the "allowed source" of the request to allow cross-domain calls:

1

res.header(&#39;Access-Control-Allow-Origin&#39;, &#39;*&#39;);

Copy after login

is set to * to allow any access source, which is not very safe. It is recommended to set it to the second-level domain name you need, such as jafeney.com.

In addition to "Allow source", others include "Allow header", "Allow domain", "Allow method", "Text type", etc. Commonly used settings are as follows:

1

2

3

4

5

6

7

function allowCross(res) {

  res.header(&#39;Access-Control-Allow-Origin&#39;, &#39;*&#39;); 

  res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');

  res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');

  res.header("X-Powered-By",' 3.2.1')

  res.header("Content-Type", "application/json;charset=utf-8");

}

Copy after login

Ajax request under ES6

Ajax request under ES6 style is different from ES5. It will be sent before the formal request is sent. A request of type OPTIONS is used as a trial. Only when the request passes, a formal request can be sent to the server.

所以服务端路由 我们还需要 处理这样一个 请求:

1

2

3

4

5

6

7

8

router.options('*', function (req, res, next) {

  res.header(&#39;Access-Control-Allow-Origin&#39;, &#39;*&#39;);

  res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');

  res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');

  res.header("X-Powered-By",' 3.2.1')

  res.header("Content-Type", "application/json;charset=utf-8");

  next();

});

Copy after login

注意:该请求同样需要设置跨域。

处理上传

处理上传的图片引人了multiparty模块,用法很简单:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

/*使用multiparty处理上传的图片*/

router.post(&#39;/upload&#39;, function(req, res, next) {

  // 生成multiparty对象,并配置上传目标路径

  var form = new multiparty.Form({uploadDir: &#39;./public/file/&#39;});

  // 上传完成后处理

  form.parse(req, function(err, fields, files) {

    var filesTmp = JSON.stringify(files, null, 2);

    var relPath = &#39;&#39;;

    if (err) {

      // 保存失败

      console.log(&#39;Parse error: &#39; + err);

    } else {

      // 图片保存成功!

      console.log(&#39;Parse Files: &#39; + filesTmp);

      // 图片处理

      processImg(files);

    }

  });

});

Copy after login

图片处理

Node处理图片需要引入 gm 模块,它需要用 npm 来安装

1

npm install gm --save

Copy after login

BUG说明

注意:node的图形操作gm模块前使用必须 先安装 imagemagick 和 graphicsmagick,Linux (ubuntu)上使用apt-get 安装:

1

2

sudo apt-get install imagemagick

sudo apt-get install graphicsmagick --with-webp // 支持webp格式的图片

Copy after login

MacOS上可以用 Homebrew 直接安装:

1

2

brew install imagemagick

brew install graphicsmagick --with-webp  // 支持webp格式的图片

Copy after login

预设尺寸

有些时候除了原图,我们可能需要把原图等比例缩小作为预览图或者缩略图。这个异步操作还是用Promise来实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

function reSizeImage(paths, dstPath, size) {

  return new Promise(function(resolve, reject) {

    gm(dstPath)

    .noProfile()

    .resizeExact(size)

    .write(&#39;.&#39; + paths[1] + &#39;@&#39; + size + &#39;00.&#39; + paths[2], function (err) {

      if (!err) {

        console.log(&#39;resize as &#39; + size + &#39; ok!&#39;)

        resolve()

      }

      else {

        reject(err)

      };

    });

  });

}

Copy after login

重命名图片

为了方便排序和管理图片,我们按照 “年月日 + 时间戳 + 尺寸” 来命名图片:

1

2

var _dateSymbol = new Date().toLocaleDateString().split(&#39;-&#39;).join(&#39;&#39;);

var _timeSymbol = new Date().getTime().toString();

Copy after login

至于图片尺寸 使用 gm的 size() 方法来获取,处理方式如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

gm(uploadedPath).size(function(err, size) {

  var dstPath = &#39;./public/file/&#39; + _dateSymbol + _timeSymbol

    + &#39;_&#39; + size.width + &#39;x&#39; + size.height + &#39;.&#39;

    + _img.originalFilename.split(&#39;.&#39;)[1];

  var _port = process.env.PORT || &#39;9999&#39;;

    relPath = &#39;http://&#39; + req.hostname + ( _port!==80 ? &#39;:&#39; + _port : &#39;&#39; )

    + &#39;/file/&#39; + _dateSymbol + _timeSymbol + &#39;_&#39; + size.width + &#39;x&#39;

    + size.height + &#39;.&#39; + _img.originalFilename.split(&#39;.&#39;)[1];

  // 重命名

  fs.rename(uploadedPath, dstPath, function(err) {

    if (err) {

      reject(err)

    } else {

      console.log(&#39;rename ok!&#39;);

    }

  });

});

Copy after login

总结

对于大前端的工作,理解图片上传的前后端原理仅仅是浅层的。我们的口号是 “把JavaScript进行到底!”,现在无论是 ReactNative的移动端开发,还是NodeJS的后端开发,前端工程师可以做的工作早已不仅仅是局限于web页面,它已经渗透到了互联网应用层面的方方面面,或许,叫 全栈工程师 更为贴切吧。

【相关推荐】

1. 免费js在线视频教程

2. JavaScript中文参考手册

3. php.cn独孤九贱(3)-JavaScript视频教程

The above is the detailed content of Share an example tutorial on the implementation principle of the image upload component (React + Node). For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

How to build a real-time chat app with React and WebSocket How to build a real-time chat app with React and WebSocket Sep 26, 2023 pm 07:46 PM

How to build a real-time chat application using React and WebSocket Introduction: With the rapid development of the Internet, real-time communication has attracted more and more attention. Live chat apps have become an integral part of modern social and work life. This article will introduce how to build a simple real-time chat application using React and WebSocket, and provide specific code examples. 1. Technical preparation Before starting to build a real-time chat application, we need to prepare the following technologies and tools: React: one for building

Guide to React front-end and back-end separation: How to achieve decoupling and independent deployment of front-end and back-end Guide to React front-end and back-end separation: How to achieve decoupling and independent deployment of front-end and back-end Sep 28, 2023 am 10:48 AM

React front-end and back-end separation guide: How to achieve front-end and back-end decoupling and independent deployment, specific code examples are required In today's web development environment, front-end and back-end separation has become a trend. By separating front-end and back-end code, development work can be made more flexible, efficient, and facilitate team collaboration. This article will introduce how to use React to achieve front-end and back-end separation, thereby achieving the goals of decoupling and independent deployment. First, we need to understand what front-end and back-end separation is. In the traditional web development model, the front-end and back-end are coupled

How to build simple and easy-to-use web applications with React and Flask How to build simple and easy-to-use web applications with React and Flask Sep 27, 2023 am 11:09 AM

How to use React and Flask to build simple and easy-to-use web applications Introduction: With the development of the Internet, the needs of web applications are becoming more and more diverse and complex. In order to meet user requirements for ease of use and performance, it is becoming increasingly important to use modern technology stacks to build network applications. React and Flask are two very popular frameworks for front-end and back-end development, and they work well together to build simple and easy-to-use web applications. This article will detail how to leverage React and Flask

How to build a reliable messaging app with React and RabbitMQ How to build a reliable messaging app with React and RabbitMQ Sep 28, 2023 pm 08:24 PM

How to build a reliable messaging application with React and RabbitMQ Introduction: Modern applications need to support reliable messaging to achieve features such as real-time updates and data synchronization. React is a popular JavaScript library for building user interfaces, while RabbitMQ is a reliable messaging middleware. This article will introduce how to combine React and RabbitMQ to build a reliable messaging application, and provide specific code examples. RabbitMQ overview:

React Router User Guide: How to implement front-end routing control React Router User Guide: How to implement front-end routing control Sep 29, 2023 pm 05:45 PM

ReactRouter User Guide: How to Implement Front-End Routing Control With the popularity of single-page applications, front-end routing has become an important part that cannot be ignored. As the most popular routing library in the React ecosystem, ReactRouter provides rich functions and easy-to-use APIs, making the implementation of front-end routing very simple and flexible. This article will introduce how to use ReactRouter and provide some specific code examples. To install ReactRouter first, we need

How to build a fast data analysis application using React and Google BigQuery How to build a fast data analysis application using React and Google BigQuery Sep 26, 2023 pm 06:12 PM

How to use React and Google BigQuery to build fast data analysis applications Introduction: In today's era of information explosion, data analysis has become an indispensable link in various industries. Among them, building fast and efficient data analysis applications has become the goal pursued by many companies and individuals. This article will introduce how to use React and Google BigQuery to build a fast data analysis application, and provide detailed code examples. 1. Overview React is a tool for building

WeChat applet implements image upload function WeChat applet implements image upload function Nov 21, 2023 am 09:08 AM

WeChat applet implements picture upload function With the development of mobile Internet, WeChat applet has become an indispensable part of people's lives. WeChat mini programs not only provide a wealth of application scenarios, but also support developer-defined functions, including image upload functions. This article will introduce how to implement the image upload function in the WeChat applet and provide specific code examples. 1. Preparatory work Before starting to write code, we need to download and install the WeChat developer tools and register as a WeChat developer. At the same time, you also need to understand WeChat

Pi Node Teaching: What is a Pi Node? How to install and set up Pi Node? Pi Node Teaching: What is a Pi Node? How to install and set up Pi Node? Mar 05, 2025 pm 05:57 PM

Detailed explanation and installation guide for PiNetwork nodes This article will introduce the PiNetwork ecosystem in detail - Pi nodes, a key role in the PiNetwork ecosystem, and provide complete steps for installation and configuration. After the launch of the PiNetwork blockchain test network, Pi nodes have become an important part of many pioneers actively participating in the testing, preparing for the upcoming main network release. If you don’t know PiNetwork yet, please refer to what is Picoin? What is the price for listing? Pi usage, mining and security analysis. What is PiNetwork? The PiNetwork project started in 2019 and owns its exclusive cryptocurrency Pi Coin. The project aims to create a one that everyone can participate

See all articles