成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

基于Node的React圖片上傳組件實(shí)現(xiàn)

cfanr / 2160人閱讀

摘要:常用的設(shè)置如下下的請求風(fēng)格下的請求和不太一樣,在正式的請求發(fā)出之前都會先發(fā)一個類型為的請求作為試探,只有當(dāng)該請求通過以后,正式的請求才能發(fā)向服務(wù)端。所以服務(wù)端路由我們還需要處理這樣一個請求注意該請求同樣需要設(shè)置跨域。

寫在前面

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

前端實(shí)現(xiàn)

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

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

//......

render() {
    return (
        
) }

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

渲染頁面

組件render部分需要體現(xiàn)三個功能:

圖片選?。╠ialog窗口)

可拖拽功能(拖拽容器)

可預(yù)覽(預(yù)覽列表)

上傳按鈕 (button)

上傳完成圖片地址和鏈接 (信息列表)

render函數(shù)
render() {
    return (
        
this.handleChange(v)} type="file" size={this.state.size} name="fileSelect" accept="image/*" multiple={this.state.multiple} /> this.handleDragHover(e)} onDragLeave={(e)=>this.handleDragHover(e)} onDrop={(e)=>this.handleDrop(e)} className="upload-drag-area"> 或者將圖片拖到此處
{ this._renderPreview(); // 渲染圖片預(yù)覽列表 }
{ this._renderUploadInfos(); // 渲染圖片上傳信息 }
) }
渲染圖片預(yù)覽列表
_renderPreview() {
    if (this.state.files) {
        return this.state.files.map((item, idx) => {
            return (
                

{item.name}

{this.state.progress[idx]}
) }) } else { return null } }
渲染圖片上傳信息列表
_renderUploadInfos() {
    if (this.state.uploadHistory) {
        return this.state.uploadHistory.map((item, idx) => {
            return (
                

上傳成功,圖片地址是: 查看

); }) } else { return null; } }
文件上傳

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

獲取文件對象

獲取文件對象需要借助 input 輸入框的 change 事件來獲取 句柄參數(shù) 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])
}
// 轉(zhuǎn)換為真正的數(shù)組
files = Array.prototype.slice.call(files, 0)
// 過濾非圖片類型的文件
files = files.filter(function (file) {
    return /image/i.test(file.type)
})

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

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

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

利用Promise處理異步上傳

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

upload(file, idx) {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest()
        if (xhr.upload) {
            // 上傳中
            xhr.upload.addEventListener("progress", (e) => {
                // 處理上傳進(jìn)度
                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)
                    // 把該文件從上傳隊(duì)列中刪除
                    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)
    })
}
上傳進(jìn)度計(jì)算

利用XMLHttpRequest對象發(fā)異步請求的好處是可以 計(jì)算請求處理的進(jìn)度,這是fetch所不具備的。
我們可以為 xhr.upload 對象的 progress 事件添加事件監(jiān)聽:

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

說明:idx參數(shù)是紀(jì)錄多圖上傳隊(duì)列的索引

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來說其實(shí)非常簡單,因?yàn)樗詭У膸讉€事件監(jiān)聽機(jī)制可以直接做這類處理。主要用到的是下面三個:

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

取消拖拽時(shí)的瀏覽器行為:

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

處理拖拽進(jìn)來的文件:

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])
    }
    // 轉(zhuǎn)換為真正的數(shù)組 
    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)})
}
多圖同時(shí)上傳

支持多圖上傳我們需要在組件調(diào)用處設(shè)置屬性:

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

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

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

好了,前端工作已經(jīng)完成,接下來就是Node的工作了。

后端實(shí)現(xiàn)

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

跨域調(diào)用

本項(xiàng)目后端采用的是express,我們可以通過 res.header() 設(shè)置 請求的 "允許源" 來允許跨域調(diào)用:

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

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

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

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風(fēng)格下的Ajax請求和ES5不太一樣,在正式的請求發(fā)出之前都會先發(fā)一個 類型為 OPTIONS的請求 作為試探,只有當(dāng)該請求通過以后,正式的請求才能發(fā)向服務(wù)端。

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

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

注意:該請求同樣需要設(shè)置跨域。

處理上傳

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

/*使用multiparty處理上傳的圖片*/
router.post("/upload", function(req, res, next) { 
    // 生成multiparty對象,并配置上傳目標(biāo)路徑
    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模塊前使用必須 先安裝 imagemagickgraphicsmagickLinux (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格式的圖片  
預(yù)設(shè)尺寸

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

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)
            };
        });
    });
}
重命名圖片

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

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

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

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!");
        }
    });
});
總結(jié)

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

當(dāng)然,全棧 兩個字的分量很重,不積跬步,無以至千里,功力低下的我還需要不斷修煉和實(shí)踐!

參考

張鑫旭 《基于HTML5的可預(yù)覽多圖片Ajax上傳》

@歡迎關(guān)注我的 github 和 個人博客 -Jafeney

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/80057.html

相關(guān)文章

  • 一些基于React、Vue、Node.js、MongoDB技術(shù)棧實(shí)踐項(xiàng)目

    摘要:利用中間件實(shí)現(xiàn)異步請求,實(shí)現(xiàn)兩個用戶角色實(shí)時(shí)通信。目前還未深入了解的一些概念。往后會寫更多的前后臺聯(lián)通的項(xiàng)目。刪除分組會連同組內(nèi)的所有圖片一起刪除。算是對自己上次用寫后臺的一個強(qiáng)化,項(xiàng)目文章在這里。后來一直沒動,前些日子才把后續(xù)的完善。 歡迎訪問我的個人網(wǎng)站:http://www.neroht.com/? 剛學(xué)vue和react時(shí),利用業(yè)余時(shí)間寫的關(guān)于這兩個框架的訓(xùn)練,都相對簡單,有的...

    tangr206 評論0 收藏0
  • 第三方庫

    摘要:微信支付,支付寶支付,銀聯(lián)支付三大支付總結(jié)支付寶植入總結(jié)支付寶的植基于和百度地圖的組件庫基于百度地圖封裝的組件庫,使用這個庫最好需要先了解和百度地圖。 Commento - 多說 & Disqus 開源替代品 Commento - 多說 & Disqus 開源替代品 anime.js 簡單入門教程 強(qiáng)大輕量的動畫庫 anime.js 入門教程 來自B站的開源的MagicaSakura源...

    seanHai 評論0 收藏0
  • 第三方庫

    摘要:微信支付,支付寶支付,銀聯(lián)支付三大支付總結(jié)支付寶植入總結(jié)支付寶的植基于和百度地圖的組件庫基于百度地圖封裝的組件庫,使用這個庫最好需要先了解和百度地圖。 Commento - 多說 & Disqus 開源替代品 Commento - 多說 & Disqus 開源替代品 anime.js 簡單入門教程 強(qiáng)大輕量的動畫庫 anime.js 入門教程 來自B站的開源的MagicaSakura源...

    gityuan 評論0 收藏0
  • GitHub 值得收藏前端項(xiàng)目[每月更新...]

    摘要:也是一款優(yōu)秀的響應(yīng)式框架站點(diǎn)所使用的一套框架為微信服務(wù)量身設(shè)計(jì)的一套框架一組很小的,響應(yīng)式的組件,你可以在網(wǎng)頁的項(xiàng)目上到處使用一個可定制的文件,使瀏覽器呈現(xiàn)的所有元素,更一致和符合現(xiàn)代標(biāo)準(zhǔn)。 GitHub 值得收藏的前端項(xiàng)目 整理與收集的一些比較優(yōu)秀github項(xiàng)目,方便自己閱讀,順便分享出來,大家一起學(xué)習(xí),本篇文章會持續(xù)更新,版權(quán)歸原作者所有。歡迎github star與fork 預(yù)...

    maxmin 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<