Heim  >  Artikel  >  Web-Frontend  >  Teilen Sie ein Beispiel-Tutorial zum Implementierungsprinzip der Bild-Upload-Komponente (React + Node).

Teilen Sie ein Beispiel-Tutorial zum Implementierungsprinzip der Bild-Upload-Komponente (React + Node).

零下一度
零下一度Original
2017-05-11 13:37:482668Durchsuche

In diesem Artikel wird hauptsächlich der ReactBildUpload-Komponentenimplementierungsbeispielcode basierend auf Node vorgestellt, der von großem praktischem Wert ist und auf den sich Freunde beziehen können, die ihn benötigen dazu

steht vorne

Wenn die rote Fahne nicht fällt, gelobe ich, JavaScript bis zum Ende auszuführen! Heute werde ich die Front-End- und Back-End-Implementierungsprinzipien (React + Node) der Bild-Upload-Komponente in meinem Open-Source-Projekt Royal vorstellen. Es hat einige Zeit gedauert und ich hoffe, es wird Ihnen hilfreich sein.

Front-End-Implementierung

Der Idee der React-Komponentisierung folgend, habe ich den Bild-Upload zu einer unabhängigen Komponente gemacht (Nr andere Abhängigkeit), importieren Sie es einfach direkt.

import React, { Component } from 'react'
import Upload from '../../components/FormControls/Upload/'

//......

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

Der uri-Parameter muss übergeben werden, der die Backend--Schnittstelle -Adresse für den Bild-Upload darstellt. Wie die Schnittstelle geschrieben wird, wird weiter unten erläutert.

Rendering-Seite

Der Render-Teil der Komponente muss drei Funktionen widerspiegeln:

  1. Bildauswahl ( Dialogfenster)

  2. Drag-fähige Funktion (Drag-Container)

  3. Vorschaufähig (Vorschauliste)

  4. Hochladen -Schaltfläche (Schaltfläche)

  5. Vollständige Bildadresse und Link (Informationsliste) hochladen

Haupt RendernFunktion

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

Vorschauliste für gerenderte Bilder

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

Liste mit Informationen zum Hochladen gerenderter Bilder

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

Datei-Upload

Das Prinzip des Bild-Uploads im Frontend besteht darin, das FormDataObjekt zu erstellen, das Dateiobjekt an das Objekt anzuhängen() und es dann bereitzustellen in XMLSend() für das HttpRequest-Objekt an den Server.

Dateiobjekt abrufen

Um das Dateiobjekt abzurufen, müssen Sie das Änderungsereignis des Eingabeeingabefelds verwenden, um das Handle abzurufen Parameter e

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

Führen Sie dann die folgende Verarbeitung durch:

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

Zu diesem Zeitpunkt ist files ein Array von Dateiobjekten, die wir benötigen, und verknüpfen es mit den Originaldateien.

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

Auf diese Weise kann der nächste Vorgang die aktuell ausgewählte Bilddatei über this.state.files abrufen.

Verwenden Sie Promise, um den asynchronen Upload durchzuführen

Der Datei-Upload erfolgt asynchron zum Browser. Um den anschließenden Upload mehrerer Bilder zu bewältigen, wird hier Promise zur Abwicklung des asynchronen Uploads eingeführt Vorgang:

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

Berechnung des Upload-Fortschritts

Der Vorteil der Verwendung des XMLHttpRequest-Objekts zum Senden asynchroner Anforderungen besteht darin, dass es den Fortschritt der Anforderungsverarbeitung berechnen kann, die abgerufen wird nicht hat.

Wir können eine Ereignisüberwachung für das Fortschrittsereignis des xhr.upload-Objekts hinzufügen:

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

Erklärung: Der idx-Parameter ist der Index , der aufzeichnet die Multi-Image-Upload-Warteschlange

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里显示
}

Drag-and-Drop-Upload

Drag-and-Drop-Dateien sind für HTML5 eigentlich sehr einfach, weil es kommt mit mehreren Ereignis-Listenern Der Mechanismus kann diese Art der Verarbeitung direkt durchführen. Die folgenden drei werden hauptsächlich verwendet:

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

Browserverhalten beim Abbrechen von Drag & Drop:

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

Verarbeitung von hineingezogenen Dateien:

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

Mehrere Bilder gleichzeitig hochladen

Mehrere Bild-Uploads unterstützen Wir müssen das Attribut am Komponentenaufrufpunkt festlegen:

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

Dann Wir können Promise.all() verwenden, um die asynchrone Operationswarteschlange zu verwalten:

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

Okay, die Front-End-Arbeit ist abgeschlossen und der nächste Schritt ist die Arbeit von Node.

Backend-Implementierung

Der Einfachheit halber verwendet das Backend das ExpressFramework, um schnell HTTP-Dienste und -Routing zu erstellen. Informationen zu bestimmten Projekten finden Sie in meinem Github-Node-Image-Upload. Obwohl die Logik einfach ist, gibt es dennoch einige bemerkenswerte Punkte:

Domänenübergreifender Aufruf

Das Backend dieses Projekts verwendet Express, wir können res .header( ) legt die „erlaubte Quelle“ der Anfrage fest, um domänenübergreifende Aufrufe zuzulassen:

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

ist auf * gesetzt, um jede Zugriffsquelle zuzulassen, die weniger sicher ist. Es wird empfohlen, den von Ihnen benötigten Domänennamen der zweiten Ebene festzulegen, z. B. jafeney.com.

Zusätzlich zu „Quelle zulassen“ gehören auch „Header zulassen“, „Domäne zulassen“, „Methode zulassen“, „Texttyp“ usw. Häufig verwendete Einstellungen sind wie folgt:

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

Ajax-Anfrage unter ES6

Ajax-Anfrage im ES6-Stil unterscheidet sich vom ES5-Stil und wird vor der formellen Anfrage zurückgesetzt Zuerst wird als Test eine Anfrage vom Typ OPTIONS gesendet. Erst wenn die Anfrage erfolgreich ist, kann die formelle Anfrage an den Server gesendet werden.

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

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

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

处理上传

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

/*使用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);
    }
  });
});

图片处理

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

npm install gm --save

BUG说明

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

sudo apt-get install imagemagick
sudo 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(&#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)
      };
    });
  });
}

重命名图片

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

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

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

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

总结

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

【相关推荐】

1. 免费js在线视频教程

2. JavaScript中文参考手册

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

Das obige ist der detaillierte Inhalt vonTeilen Sie ein Beispiel-Tutorial zum Implementierungsprinzip der Bild-Upload-Komponente (React + Node).. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn