画像アップロードコンポーネント (React + Node) の実装原理に関するサンプルチュートリアルを共有します。

零下一度
リリース: 2017-05-11 13:37:48
オリジナル
2682 人が閲覧しました

この記事では、主に Node に基づいた Reactpictureupload コンポーネントの実装例を紹介します。これは非常に実用的であり、必要な方は参考にしてください

前に書いてください。赤旗は降らない、JavaScript

最後まで行くことを約束する!今日は、私のオープンソース プロジェクト Royal の画像アップロード コンポーネントのフロントエンドとバックエンドの実装原則 (React + Node) を紹介します。少し時間がかかりましたが、お役に立てれば幸いです。

フロントエンドの実装

React コンポーネント化のアイデアに従って、画像を直接インポートできる独立したコンポーネント (他の依存関係なし) にアップロードするようにしました。

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

//......

render() {
  return (
    <p><Upload uri={&#39;http://jafeney.com:9999/upload&#39;} /></p>
  )
}
ログイン後にコピー

uri パラメータを渡す必要があります。これは、画像アップロードのバックエンド

インターフェース

アドレスです。インターフェースの記述方法については後述します。


ページのレンダリング

コンポーネントのレンダリング部分は、次の 3 つの機能を反映する必要があります:

画像の選択 (ダイアログ ウィンドウ)
  1. ドラッグ可能な関数 (ドラッグ コンテナ)
  2. プレビュー可能 (プレビュー リスト) )
  3. アップロード
  4. ボタン
  5. (ボタン)

    アップロード完了画像アドレスとリンク(情報一覧)
  6. メインレンダリング
  7. 機能

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>
  )
}
ログイン後にコピー
レンダリング画像プレビュー一覧
_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
  }
}
ログイン後にコピー

レンダリング画像アップロード情報一覧

_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() をサーバーに送信します。 ファイルオブジェクトの取得

ファイルオブジェクトを取得するには、入力ボックスの変更イベント

を使用してハンドルパラメータe

onChange={(e)=>this.handleChange(e)}
ログイン後にコピー
を取得し、以下の処理を行う必要があります:
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)
})
ログイン後にコピー

At今回の files は必要なファイル オブジェクトの配列であり、それを元のファイルに連結します。

this.setState({files: this.state.files.concat(files)})
ログイン後にコピー

このようにして、次の操作は this.state.files を通じて現在選択されている画像ファイルを取得できます。

非同期アップロードを処理するには Promise を使用します

ファイルのアップロードはブラウザに対して非同期です。後続の複数画像のアップロードを処理するために、非同期操作を処理するために Promise が導入されています。 XMLHttpRequest オブジェクトを使用して非同期リクエストを行う利点は、フェッチにはないリクエスト処理の進行状況を計算できることです。

xhr.upload オブジェクトの進行状況イベントのイベント監視を追加できます:

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)
  })
}
ログイン後にコピー

説明: idx パラメーターは、複数画像のアップロード キューを記録する インデックスです

xhr.upload.addEventListener("progress", (e) => {
  // 处理上传进度
  this.handleProgress(file, e.loaded, e.total, i);
}, false)
ログイン後にコピー


ドラッグ アンド ドロップ アップロード

HTML5 用のファイルのドラッグ アンド ドロップ

このタイプの処理を直接処理できるいくつかのイベント リスニング メカニズムが付属しているため、これは実際には非常に簡単です。主に次の 3 つが使用されます:

ドラッグ アンド ドロップをキャンセルしたときのブラウザの動作:

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)})
}
ログイン後にコピー

その後、Promise.all() を使用して非同期操作キューを処理できます:

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

さて、フロントエンドの作業は完了しました。次のステップは作業です。ノードの。

バックエンドの実装

便宜上、バックエンドは Express

フレームワーク

を使用して、HTTP サービスとルーティングを迅速に構築します。特定のプロジェクトについては、私の github のノードイメージアップロードを参照してください。ロジックは単純ですが、いくつかの注目すべき点があります:

クロスドメイン呼び出し

このプロジェクトのバックエンドは、res.header() を通じてリクエストの「許可されたソース」を設定できます。 -domain 呼び出し:

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) })
}
ログイン後にコピー

* に設定すると、あらゆるアクセス ソースが許可されますが、安全性は低くなります。 jafeney.com など、必要な第 2 レベルのドメイン名に設定することをお勧めします。 「ソースを許可」の他に、「ヘッダーを許可」、「ドメインを許可」、「メソッドを許可」、「テキストタイプ」などが含まれます。一般的に使用される設定は次のとおりです。

res.header(&#39;Access-Control-Allow-Origin&#39;, &#39;*&#39;);
ログイン後にコピー

ES6 での Ajax リクエスト

ES6 での Ajax リクエストは、正式なリクエストが発行される前に、テストとして OPTIONS タイプのリクエストが送信されます。リクエストが成功すると、正式なリクエストをサーバーに送信できます。

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

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视频教程

以上が画像アップロードコンポーネント (React + Node) の実装原理に関するサンプルチュートリアルを共有します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート