国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

基于Node的React圖片上傳組件實現實例代碼

2019-11-19 16:37:00
字體:
來源:轉載
供稿:網友

寫在前面

紅旗不倒,誓把JavaScript進行到底!今天介紹我的開源項目 Royal 里的圖片上傳組件的前后端實現原理(React + Node),花了一些時間,希望對你有所幫助。

前端實現

遵循React 組件化的思想,我把圖片上傳做成了一個獨立的組件(沒有其他依賴),直接import即可。

import React, { Component } from 'react'import Upload from '../../components/FormControls/Upload/'//......render() {  return (    <div><Upload uri={'http://jafeney.com:9999/upload'} /></div>  )}

 uri 參數是必須傳的,是圖片上傳的后端接口地址,接口怎么寫下面會講到。

渲染頁面

組件render部分需要體現三個功能:

  1. 圖片選取(dialog窗口)
  2. 可拖拽功能(拖拽容器)
  3. 可預覽(預覽列表)
  4. 上傳按鈕 (button)
  5. 上傳完成圖片地址和鏈接 (信息列表)

主render函數

render() {  return (    <form action={this.state.uri} method="post" encType="multipart/form-data">      <div className="ry-upload-box">        <div className="upload-main">          <div 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>          </div>          <div className={this.state.files.length?             "upload-preview":"upload-preview ry-hidden"}>            { this._renderPreview();  // 渲染圖片預覽列表 }          </div>        </div>        <div className={this.state.files.length?           "upload-submit":"upload-submit ry-hidden"}>           <button type="button"             onClick={()=>this.handleUpload()}             class="upload-submit-btn">             確認上傳圖片           </button>        </div>        <div className="upload-info">          { this._renderUploadInfos();  // 渲染圖片上傳信息 }        </div>      </div>    </form>  )}

渲染圖片預覽列表

_renderPreview() {  if (this.state.files) {    return this.state.files.map((item, idx) => {      return (        <div 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>         </div>      )    })  } else {    return null  }}

渲染圖片上傳信息列表

_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;  }}

文件上傳

前端要實現圖片上傳的原理就是通過構建FormData對象,把文件對象append()到該對象,然后掛載在XMLHttpRequest對象上 send() 到服務端。

獲取文件對象

獲取文件對象需要借助 input 輸入框的 change 事件來獲取 句柄參數 e

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

然后做以下處理:

e.preventDefault()let target = event.targetlet files = target.fileslet count = this.state.multiple ? files.length : 1for (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)})

這時 files 就是 我們需要的文件對象組成的數組,把它 concat 到原有的 files里。

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

如此,接下來的操作 就可以 通過 this.state.files 取到當前已選中的 圖片文件。

利用Promise處理異步上傳

文件上傳對于瀏覽器來說是異步的,為了處理 接下來的多圖上傳,這里引入了 Promise來處理異步操作:

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)  })}

上傳進度計算

利用XMLHttpRequest對象發異步請求的好處是可以 計算請求處理的進度,這是fetch所不具備的。

我們可以為 xhr.upload 對象的 progress 事件添加事件監聽:

xhr.upload.addEventListener("progress", (e) => {  // 處理上傳進度  this.handleProgress(file, e.loaded, e.total, i);}, false)

說明:idx參數是紀錄多圖上傳隊列的索引

handleProgress(file, loaded, total, idx) {  let percent = (loaded / total * 100).toFixed(2) + '%';  let _progress = this.state.progress;  _progress[idx] = percent;  this.setState({ progress: _progress }) // 反饋到DOM里顯示}

拖拽上傳

拖拽文件對于HTML5來說其實非常簡單,因為它自帶的幾個事件監聽機制可以直接做這類處理。主要用到的是下面三個:

onDragOver={(e)=>this.handleDragHover(e)}onDragLeave={(e)=>this.handleDragHover(e)}onDrop={(e)=>this.handleDrop(e)}

取消拖拽時的瀏覽器行為:

handleDragHover(e) {  e.stopPropagation()  e.preventDefault()}

處理拖拽進來的文件:

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)})}

多圖同時上傳

支持多圖上傳我們需要在組件調用處設置屬性:

multiple = { true } // 開啟多圖上傳 size = { 50 }    // 一次最大上傳數量(雖沒有上限,為保證服務端正常,建議50以下)

然后我們可以使用 Promise.all() 處理異步操作隊列:

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) })}

好了,前端工作已經完成,接下來就是Node的工作了。

后端實現

為了方便,后端采用的是express框架來快速搭建Http服務和路由。具體項目見我的github node-image-upload。邏輯雖然簡單,但還是有幾個可圈可點的地方:

跨域調用

本項目后端采用的是express,我們可以通過 res.header() 設置 請求的 “允許源” 來允許跨域調用:

res.header('Access-Control-Allow-Origin', '*'); 

設置為 * 說明允許任何 訪問源,不太安全。建議設置成 你需要的 二級域名,如 jafeney.com。

除了 “允許源” ,其他還有 “允許頭” 、”允許域”、 “允許方法”、”文本類型” 等。常用的設置如下:

function allowCross(res) {  res.header('Access-Control-Allow-Origin', '*');    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");}

ES6下的Ajax請求

ES6風格下的Ajax請求和ES5不太一樣,在正式的請求發出之前都會先發一個 類型為 OPTIONS的請求 作為試探,只有當該請求通過以后,正式的請求才能發向服務端。

所以服務端路由 我們還需要 處理這樣一個 請求:

router.options('*', function (req, res, next) {  res.header('Access-Control-Allow-Origin', '*');  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();});

注意:該請求同樣需要設置跨域。

處理上傳

處理上傳的圖片引人了multiparty模塊,用法很簡單:

/*使用multiparty處理上傳的圖片*/router.post('/upload', function(req, res, next) {   // 生成multiparty對象,并配置上傳目標路徑  var form = new multiparty.Form({uploadDir: './public/file/'});  // 上傳完成后處理  form.parse(req, function(err, fields, files) {    var filesTmp = JSON.stringify(files, null, 2);    var relPath = '';    if (err) {      // 保存失敗       console.log('Parse error: ' + err);    } else {      // 圖片保存成功!      console.log('Parse Files: ' + filesTmp);      // 圖片處理      processImg(files);    }  });});

圖片處理

Node處理圖片需要引入 gm 模塊,它需要用 npm 來安裝:

npm install gm --save

BUG說明

注意:node的圖形操作gm模塊前使用必須 先安裝 imagemagick 和 graphicsmagick,Linux (ubuntu)上使用apt-get 安裝:

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

MacOS上可以用 Homebrew 直接安裝:

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

預設尺寸

有些時候除了原圖,我們可能需要把原圖等比例縮小作為預覽圖或者縮略圖。這個異步操作還是用Promise來實現:

function reSizeImage(paths, dstPath, size) {  return new Promise(function(resolve, reject) {    gm(dstPath)    .noProfile()    .resizeExact(size)    .write('.' + paths[1] + '@' + size + '00.' + paths[2], function (err) {      if (!err) {        console.log('resize as ' + size + ' ok!')        resolve()      }      else {        reject(err)      };    });  });}

重命名圖片

為了方便排序和管理圖片,我們按照 “年月日 + 時間戳 + 尺寸” 來命名圖片:

var _dateSymbol = new Date().toLocaleDateString().split('-').join('');var _timeSymbol = new Date().getTime().toString();

至于圖片尺寸 使用 gm的 size() 方法來獲取,處理方式如下:

gm(uploadedPath).size(function(err, size) {  var dstPath = './public/file/' + _dateSymbol + _timeSymbol     + '_' + size.width + 'x' + size.height + '.'     + _img.originalFilename.split('.')[1];  var _port = process.env.PORT || '9999';    relPath = 'http://' + req.hostname + ( _port!==80 ? ':' + _port : '' )     + '/file/' + _dateSymbol + _timeSymbol + '_' + size.width + 'x'     + size.height + '.' + _img.originalFilename.split('.')[1];  // 重命名  fs.rename(uploadedPath, dstPath, function(err) {    if (err) {      reject(err)    } else {      console.log('rename ok!');    }  });});

總結

對于大前端的工作,理解圖片上傳的前后端原理僅僅是淺層的。我們的口號是 “把JavaScript進行到底!”,現在無論是 ReactNative的移動端開發,還是NodeJS的后端開發,前端工程師可以做的工作早已不僅僅是局限于web頁面,它已經滲透到了互聯網應用層面的方方面面,或許,叫 全棧工程師 更為貼切吧。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 沙雅县| 望城县| 黄大仙区| 通化县| 乌兰浩特市| 奎屯市| 衡阳县| 岳阳市| 岑巩县| 上虞市| 南漳县| 称多县| 峨眉山市| 三门峡市| 驻马店市| 开原市| 北流市| 庐江县| 新疆| 绥化市| 梅河口市| 山丹县| 香格里拉县| 海阳市| 会理县| 长沙市| 石渠县| 安康市| 库尔勒市| 平湖市| 合作市| 理塘县| 普兰店市| 西安市| 河南省| 大荔县| 阿鲁科尔沁旗| 禄丰县| 金堂县| 九龙县| 大新县|