이번 글에서는 Node 기반의 React그림upload 컴포넌트 구현 예제 코드를 주로 소개하고 있어 실용성이 매우 큰 친구들이 참고할 수 있습니다. to it
앞에
적기가 떨어지지 않는다면 JavaScript를 끝까지 수행할 것을 다짐합니다! 오늘은 저의 오픈소스 프로젝트인 Royal의 이미지 업로드 컴포넌트의 프론트엔드와 백엔드 구현 원리(React + Node)를 소개하겠습니다. 시간이 좀 걸렸는데 도움이 되셨으면 좋겠습니다.

프런트엔드 구현
React 컴포넌트화라는 아이디어에 따라 이미지 업로드를 독립 컴포넌트로 만들었습니다. 다른 종속성), 직접 가져오면 됩니다.
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={'http:
)
}
|
로그인 후 복사
이미지 업로드를 위한 백엔드 인터페이스 주소인 uri 매개변수를 전달해야 합니다. 인터페이스 작성 방법은 아래에서 설명합니다.
렌더링 페이지
구성 요소의 렌더링 부분은 세 가지 기능을 반영해야 합니다.
이미지 선택( 대화창)
드래그 기능(드래그 컨테이너)
미리보기 가능(미리보기 목록)
업로드버튼(버튼)
완성된 이미지 주소 및 링크(정보목록) 업로드
메인 render기능
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>
)
}
|
로그인 후 복사
렌더링 이미지 미리보기 목록
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
}
}
|
로그인 후 복사
렌더링 이미지 업로드 정보 목록
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;
}
}
|
로그인 후 복사
파일 업로드
프런트 엔드에서 이미지를 업로드하는 원리는 FormData객체를 빌드하고 해당 객체에 파일 객체를 추가()한 후 XML에 마운트하는 것입니다. HttpRequest 객체는 서버로 전송( )합니다.
파일 객체 가져오기
파일 객체를 가져오려면 입력 입력 상자의 변경이벤트를 사용하여 핸들을 가져와야 합니다. 매개변수 e
1 | onChange={(e)=>this.handleChange(e)}
|
로그인 후 복사
그런 다음 다음 처리를 수행합니다.
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)
})
|
로그인 후 복사
이때 files는 필요한 파일 객체의 배열이며 이를 원본 파일에 연결합니다.
1 | this.setState({files: this.state.files.concat(files)})
|
로그인 후 복사
이런 식으로 다음 작업에서는 this.state.files를 통해 현재 선택된 이미지 파일을 가져올 수 있습니다.
Promise를 사용하여 비동기식 업로드 처리
파일 업로드는 브라우저에 대해 비동기식입니다. 후속 다중 이미지 업로드를 처리하기 위해 여기에 비동기식을 처리하기 위한 Promise가 도입되었습니다. 작업:
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)
})
}
|
로그인 후 복사
업로드 진행률 계산
XMLHttpRequest 개체를 사용하여 비동기 요청을 보내는 이점은 가져오기에서는 수행되지 않는 요청 처리 진행률을 계산할 수 있다는 것입니다. 가지다.
xhr.upload 객체의 진행 이벤트에 대한 이벤트 모니터링을 추가할 수 있습니다:
1 2 3 4 | xhr.upload.addEventListener( "progress" , (e) => {
this.handleProgress(file, e.loaded, e.total, i);
}, false)
|
로그인 후 복사
설명: idx 매개변수는 다중 이미지 업로드를 기록하는 인덱스입니다. queue
1 2 3 4 5 6 | handleProgress(file, loaded, total, idx) {
let percent = (loaded / total * 100).toFixed(2) + '%';
let _progress = this.state.progress;
_progress[idx] = percent;
this.setState({ progress: _progress })
}
|
로그인 후 복사
드래그 앤 드롭 업로드
드래그 앤 드롭 파일은 실제로 HTML5에서 매우 간단합니다. 이러한 유형의 처리를 직접 수행할 수 있는 여러 이벤트 청취 메커니즘이 제공됩니다. 주로 다음 세 가지가 사용됩니다.
1 2 3 | onDragOver={(e)=>this.handleDragHover(e)}
onDragLeave={(e)=>this.handleDragHover(e)}
onDrop={(e)=>this.handleDrop(e)}
|
로그인 후 복사
드래그 앤 드롭 취소 시 브라우저 동작:
1 2 3 4 | handleDragHover(e) {
e.stopPropagation()
e.preventDefault()
}
|
로그인 후 복사
드래그된 파일 처리:
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)})
}
|
로그인 후 복사
추가 이미지 동시 업로드
는 여러 이미지 업로드를 지원합니다. 구성 요소 호출 지점에서 속성 을 설정해야 합니다.
1 2 | multiple = { true }
size = { 50 }
|
로그인 후 복사
그런 다음 Promise.all()을 사용하여 비동기 처리를 수행할 수 있습니다. 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) })
}
|
로그인 후 복사
자, 프론트엔드 작업은 완료되었고 다음 단계는 Node.js 작업입니다.
백엔드 구현
편의를 위해 백엔드는 Express프레임워크를 사용하여 Http 서비스 및 라우팅을 빠르게 구축합니다. 특정 프로젝트에 대해서는 내 github node-image-upload를 참조하세요. 논리는 간단하지만 여전히 몇 가지 주목할 만한 점이 있습니다.
교차 도메인 호출
이 프로젝트의 백엔드는 express를 사용하므로 res .header( )는 요청의 "허용된 소스"를 설정하여 도메인 간 호출을 허용합니다.
1 | res.header('Access-Control-Allow-Origin', '*');
|
로그인 후 복사
는 *로 설정되어 모든 액세스 소스를 허용하므로 매우 안전하지 않습니다. jafeney.com과 같이 필요한 2차 도메인 이름으로 설정하는 것이 좋습니다.
"소스 허용" 외에 "헤더 허용", "도메인 허용", "방법 허용", "텍스트 유형" 등이 있습니다. 일반적으로 사용되는 설정은 다음과 같습니다.
1 2 3 4 5 6 7 | 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 유형의 요청은 요청이 통과된 경우에만 정식 요청이 서버로 전송될 수 있습니다.
所以服务端路由 我们还需要 处理这样一个 请求:
1 2 3 4 5 6 7 8 | 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模块,用法很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | router.post('/upload', function (req, res, next) {
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 来安装:
BUG说明
注意:node的图形操作gm模块前使用必须 先安装 imagemagick 和 graphicsmagick,Linux (ubuntu)上使用apt-get 安装:
1 2 | sudo apt-get install imagemagick
sudo apt-get install graphicsmagick --with-webp
|
로그인 후 복사
MacOS上可以用 Homebrew 直接安装:
1 2 | brew install imagemagick
brew install graphicsmagick --with-webp
|
로그인 후 복사
预设尺寸
有些时候除了原图,我们可能需要把原图等比例缩小作为预览图或者缩略图。这个异步操作还是用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('.' + paths[1] + '@' + size + '00.' + paths[2], function (err) {
if (!err) {
console.log('resize as ' + size + ' ok!')
resolve()
}
else {
reject(err)
};
});
});
}
|
로그인 후 복사
重命名图片
为了方便排序和管理图片,我们按照 “年月日 + 时间戳 + 尺寸” 来命名图片:
1 2 | var _dateSymbol = new Date ().toLocaleDateString().split('-').join('');
var _timeSymbol = new Date ().getTime().toString();
|
로그인 후 복사
至于图片尺寸 使用 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 = './ public /file/' + _dateSymbol + _timeSymbol
+ '_' + size.width + 'x' + size.height + '.'
+ _img.originalFilename.split('.')[1];
var _port = process.env.PORT || '9999';
relPath = 'http:
+ '/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页面,它已经渗透到了互联网应用层面的方方面面,或许,叫 全栈工程师 更为贴切吧。
【相关推荐】
1. 免费js在线视频教程
2. JavaScript中文参考手册
3. php.cn独孤九贱(3)-JavaScript视频教程
위 내용은 이미지 업로드 구성 요소(React + Node)의 구현 원리에 대한 예제 튜토리얼 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!