>  기사  >  웹 프론트엔드  >  이미지 업로드 구성 요소(React + Node)의 구현 원리에 대한 예제 튜토리얼 공유

이미지 업로드 구성 요소(React + Node)의 구현 원리에 대한 예제 튜토리얼 공유

零下一度
零下一度원래의
2017-05-11 13:37:482670검색

이번 글에서는 Node 기반의 React그림upload 컴포넌트 구현 예제 코드를 주로 소개하고 있어 실용성이 매우 큰 친구들이 참고할 수 있습니다. to it

앞에

적기가 떨어지지 않는다면 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 매개변수를 전달해야 합니다. 인터페이스 작성 방법은 아래에서 설명합니다.

렌더링 페이지

구성 요소의 렌더링 부분은 세 가지 기능을 반영해야 합니다.

  1. 이미지 선택( 대화창)

  2. 드래그 기능(드래그 컨테이너)

  3. 미리보기 가능(미리보기 목록)

  4. 업로드버튼(버튼)

  5. 완성된 이미지 주소 및 링크(정보목록) 업로드

메인 render기능

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객체를 빌드하고 해당 객체에 파일 객체를 추가()한 후 XML에 마운트하는 것입니다. HttpRequest 객체는 서버로 전송( )합니다.

파일 객체 가져오기

파일 객체를 가져오려면 입력 입력 상자의 변경이벤트를 사용하여 핸들을 가져와야 합니다. 매개변수 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)
})

이때 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 개체를 사용하여 비동기 요청을 보내는 이점은 가져오기에서는 수행되지 않는 요청 처리 진행률을 계산할 수 있다는 것입니다. 가지다.

xhr.upload 객체의 진행 이벤트에 대한 이벤트 모니터링을 추가할 수 있습니다:

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

설명: idx 매개변수는 다중 이미지 업로드를 기록하는 인덱스입니다. queue

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

드래그 앤 드롭 업로드

드래그 앤 드롭 파일은 실제로 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()을 사용하여 비동기 처리를 수행할 수 있습니다. Operations Queue:

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.js 작업입니다.

백엔드 구현

편의를 위해 백엔드는 Express프레임워크를 사용하여 Http 서비스 및 라우팅을 빠르게 구축합니다. 특정 프로젝트에 대해서는 내 github node-image-upload를 참조하세요. 논리는 간단하지만 여전히 몇 가지 주목할 만한 점이 있습니다.

교차 도메인 호출

이 프로젝트의 백엔드는 express를 사용하므로 res .header( )는 요청의 "허용된 소스"를 설정하여 도메인 간 호출을 허용합니다.

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

는 *로 설정되어 모든 액세스 소스를 허용하므로 매우 안전하지 않습니다. jafeney.com과 같이 필요한 2차 도메인 이름으로 설정하는 것이 좋습니다.

"소스 허용" 외에 "헤더 허용", "도메인 허용", "방법 허용", "텍스트 유형" 등이 있습니다. 일반적으로 사용되는 설정은 다음과 같습니다.

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

ES6의 Ajax 요청

ES6 스타일의 Ajax 요청은 ES5와 다릅니다. 정식 요청이 전송되기 전에 전송됩니다. . 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.