ElementUI?Upload源碼組件上傳流程解析
引言
很多時候我們都一直使用ElementUI
的Upload
上傳組件進行二次封裝, 但是否知道內(nèi)部是什么樣的一個上傳流程,事件在哪個時機觸發(fā),從獲取文件到上傳結束究竟經(jīng)歷什么樣的一個過程?希望通過分析該組件的核心邏輯 (不包括UI邏輯) 讓你在后續(xù)的開發(fā)中能夠快速定位問題所在
源文件
訪問packages/upload
目錄可以看到如下內(nèi)容,其主要核心代碼在upload.vue
和index.vue
,單純一個文件一個文件看代碼理解雖然能看得懂,但是比較難把整個邏輯串通,所以我們從文件的獲取到上傳結束開始逐一分析
│ index.js └─ src ajax.js [默認上傳請求工具] index.vue [管理FileList數(shù)據(jù),對外暴露操作文件列表的方法] upload-dragger.vue [拖拽:對文件獲取的邏輯] upload-list.vue [文件列表:純UI組件根據(jù)不同listType展示不同的樣式] upload.vue [對單個文件上傳處理的過程],會涉及index.vue文件邏輯操作]
流程圖
1??. 獲取文件
??? upload.vue
創(chuàng)建input組件同時設置display:none
進行隱藏,只通過ref
進行引用觸發(fā)$refs.input.click()
,通過監(jiān)聽(13: Enter鍵)
32: (空格鍵)
和點擊上傳容器觸發(fā)
handleClick
handleKeydown
拖拽
(只是在拖拽結束獲取文件觸發(fā)uploadFiles,具體邏輯在upload-dragger.vue比較簡單,所以不對其進行分析)
methods: { handleClick() { if (!this.disabled) { // 處理選中文件之后,后續(xù)繼續(xù)選中重復文件無法觸發(fā)change問題 this.$refs.input.value = null; this.$refs.input.click(); } }, handleKeydown(e) { if (e.target !== e.currentTarget) return; if (e.keyCode === 13 || e.keyCode === 32) { this.handleClick(); } } }, render(h) { // ... const data = { on: { click: handleClick, keydown: handleKeydown } }; return ( <div {...data} tabindex="0" > // ... <input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input> </div> ); }
2??. 文件個數(shù)校驗
在觸發(fā)input的handleChange
后開始我們的校驗階段(uploadFiles方法
):
?? 校驗文件最大個數(shù)
如果有設置limit
個數(shù)限制時,判斷當前選中的文件和已有的文件總和是否超出最大個數(shù),是的話則觸發(fā)onExceed事件
同時退出
// ??? upload.vue uploadFiles if (this.limit && this.fileList.length + files.length > this.limit) { this.onExceed && this.onExceed(files, this.fileList); return; }
?? 非多文件情況處理
如果multiple
未設置或者為false
時,只獲取選中的第一個文件,如果沒有選中的文件則退出
// ??? upload.vue uploadFiles let postFiles = Array.prototype.slice.call(files); if (!this.multiple) { postFiles = postFiles.slice(0, 1); } if (postFiles.length === 0) { return; }
3??. 構造FileItem對象
onStart
handleStart
FileItem對象
key | 描述 |
---|---|
status | 文件狀態(tài): ready, uploading, success, fail |
name | 文件名稱 |
size | 文件大小 |
percentage | 上傳進度 |
uid | 文件uid |
raw | 原始文件對象 |
遍歷每一個選中的文件,根據(jù)我們需要的信息構建我們所的文件對象同時放入fileList
數(shù)組中,同時status
狀態(tài)為ready
準備上傳階段,判斷如果listType=picture-card | picture
根據(jù)文件設置url: 生成blobURL
進行回顯 (不需要等待上傳完成才能看到圖片內(nèi)容), 接著觸發(fā)onChange
事件
// upload.vue (onStart) ---> index.vue (handleStart) handleStart(rawFile) { rawFile.uid = Date.now() + this.tempIndex++; let file = { status: 'ready', name: rawFile.name, size: rawFile.size, percentage: 0, uid: rawFile.uid, raw: rawFile }; if (this.listType === 'picture-card' || this.listType === 'picture') { try { file.url = URL.createObjectURL(rawFile); } catch (err) { console.error('[Element Error][Upload]', err); return; } } this.uploadFiles.push(file); this.onChange(file, this.uploadFiles); }
4??. 上傳階段
緊接著判斷auto-upload
是否自動上傳
- true : 自動觸發(fā)
upload
方法 - false: 通過外部手動觸發(fā)
$refs.upload.submit()
手動觸發(fā)時通過過濾出status=ready
準備上傳的文件遍歷觸發(fā)upload
方法
// index.vue submit() { this.uploadFiles .filter(file => file.status === 'ready') .forEach(file => { this.$refs['upload-inner'].upload(file.raw); }); }
?? beforeUpload前置操作
根據(jù)外部是否傳入beforeUpload
,可以對預備上傳的文件進行預處理或者校驗是否可以上傳:
- 不傳
beforeUpload
直接觸發(fā)post
方法上傳
// ??? upload.vue upload if (!this.beforeUpload) { return this.post(rawFile); }
傳beforeUpload
(同步
和異步
):
- 同步:傳入一個方法后返回false即終止上傳,其他非Promise類型結果的都通過上傳
- 異步:當被reject則中斷上傳過程,當
resolve
f返回一個新的文件或者Blob類型數(shù)據(jù),根據(jù)原始的文件對象,重新構建出新的文件對象進行上傳(resolve返回任意值都會觸發(fā)上傳)
// ??? upload.vue upload const before = this.beforeUpload(rawFile); // Promise if (before && before.then) { before.then(processedFile => { const fileType = Object.prototype.toString.call(processedFile); if (fileType === '[object File]' || fileType === '[object Blob]') { if (fileType === '[object Blob]') { processedFile = new File([processedFile], rawFile.name, { type: rawFile.type }); } // 將原始值復制到新構建的File對象中 for (const p in rawFile) { if (rawFile.hasOwnProperty(p)) { processedFile[p] = rawFile[p]; } } this.post(processedFile); } else { this.post(rawFile); } }, () => { // 停止上傳并且從文件列表中刪除 this.onRemove(null, rawFile); }); } // 非false正常上傳 else if (before !== false) { this.post(rawFile); } else { // 停止上傳并且從文件列表中刪除 this.onRemove(null, rawFile); }
? beforeUpload中斷情況
onRemove
handleRemove
beforeRemove可選
doRemove
當beforeUpload
返回false
或者reject
時,會觸發(fā)onRemove
方法(index.vue: handleRemove
)
?? 這里觸發(fā)onRemove其實有個坑點,當你beforeUpload被中斷時會觸發(fā)onRemove對文件進行刪除,如果你傳入了beforeRemove同時彈出確認框確認刪除操作,這會導致上傳中斷時顯示出來,這會讓用戶感覺到突兀,有個比較粗糙的方式就是在beforeRemove判斷status!=ready,即準備上傳的文件不需要走beforeRemove確認直接刪除
// index.vue handleRemove(file, raw) { if (raw) { // 獲取當前的FileItem對象 file = this.getFile(raw); } let doRemove = () => { // 中斷正在上傳的文件 this.abort(file); // 移除當前FileItem let fileList = this.uploadFiles; fileList.splice(fileList.indexOf(file), 1); // 觸發(fā)回調(diào) this.onRemove(file, fileList); }; // 外部沒有傳入beforeRemove直接操作刪除 if (!this.beforeRemove) { doRemove(); } else if (typeof this.beforeRemove === 'function') { const before = this.beforeRemove(file, this.uploadFiles); // 和 beforeUpload類似邏輯 if (before && before.then) { before.then(() => { doRemove(); }, noop); } else if (before !== false) { doRemove(); } } }
?? 上傳
構造HttpRequest的Options參數(shù)
發(fā)起請求并且緩存當前請求實例以便后續(xù)可終止
- 構造參數(shù),即
httpRequest
需要的請求對象options
key | 描述 |
---|---|
headers | 請求headers |
withCredentials | 發(fā)送 cookie 憑證信息 |
data | 請求體數(shù)據(jù) |
filename | 文件名稱 |
action | 上傳路徑 |
onProgress | 上傳進度回調(diào) |
onSuccess | 上傳成功回調(diào) |
onError | 上傳錯誤回調(diào) |
當外部默認不傳httRequest
時,會通過內(nèi)部封裝的ajax.js
進行上傳請求(內(nèi)部實現(xiàn)并沒有說什么復雜的地方,單純的實現(xiàn)原生XMLHttpRequest
請求,這里就不對其內(nèi)容進行討論可自行了解),需要注意的是當自定義httpRequest時,要對onProgress
,onSuccess
,onError
進行回調(diào),保證結果在內(nèi)部能獲取到正常響應
// ??? upload.vue post // upload.vue post方法 const { uid } = rawFile; const options = { headers: this.headers, withCredentials: this.withCredentials, file: rawFile, data: this.data, filename: this.name, action: this.action, onProgress: e => { this.onProgress(e, rawFile); }, onSuccess: res => { this.onSuccess(res, rawFile); delete this.reqs[uid]; }, onError: err => { this.onError(err, rawFile); delete this.reqs[uid]; } }; const req = this.httpRequest(options);
- 緩存當前請求的每一個實例,同時在
http-request
的options
內(nèi)的onSuccess
和onError
回調(diào)時,對緩存的請求實例進行刪除.
??當你使用自定義httpRequest
注意點:
有返回值時,記得暴露abort
方法,因為內(nèi)部默認ajax返回的實例是有abort
方法可以中斷請求,而如果自定義時返回沒有abort
方法時,點擊刪除會導致報錯
需要在自定義httpRequest內(nèi)部合理時機調(diào)用onSuccess,onProgress,onError,因為這是FileItem.status更新的時機
this.reqs[uid] = req; if (req && req.then) { // 在最后觸發(fā)成功之后,再次調(diào)用回調(diào) req.then(options.onSuccess, options.onError); }
?? Uploading
當觸發(fā)onProgress
回調(diào)時,對FileItem
對象的status
設置為uploading
和percentage
上傳進度值,觸發(fā)外部監(jiān)聽的onProgress
回調(diào)
// index.vue handleProgress(ev, rawFile) { const file = this.getFile(rawFile); this.onProgress(ev, file, this.uploadFiles); file.status = 'uploading'; file.percentage = ev.percent || 0; }
?? Success
當觸發(fā)onSuccess
回調(diào)時,對FileItem
對象的status
設置為success
,添加response
響應值 ,與此同時觸發(fā)外部監(jiān)聽的onSuccess
,onChange
handleSuccess(res, rawFile) { const file = this.getFile(rawFile); if (file) { file.status = 'success'; file.response = res; this.onSuccess(res, file, this.uploadFiles); this.onChange(file, this.uploadFiles); } }
?? Fail
當觸發(fā)onError
回調(diào)時,對FileItem
對象的status
設置為fail
,與此同時觸發(fā)外部監(jiān)聽的onError
,onChange
,當報錯觸發(fā)handleError
會對相應報錯的FileItem
從FileList
中移除
handleError(err, rawFile) { const file = this.getFile(rawFile); const fileList = this.uploadFiles; file.status = 'fail'; fileList.splice(fileList.indexOf(file), 1); this.onError(err, file, this.uploadFiles); this.onChange(file, this.uploadFiles); }
?? Abort
由于組件對外提供了abort
中斷請求的方法,可以通過傳入當前正上傳的file對象
或者文件的uid
可中斷指定的文件上傳同時從FileList
中移除,當不傳任何參數(shù)時會對正在上傳的全部文件進行中斷
// index.vue // 對外暴露的方法 abort(file) { this.$refs['upload-inner'].abort(file); }, // upload.vue abort(file) { const { reqs } = this; // 傳了指定文件 if (file) { // 這里支持傳入是一個uid let uid = file; if (file.uid) uid = file.uid; if (reqs[uid]) { // 將指定的請求進行中斷 reqs[uid].abort(); } } else { // 不傳參數(shù)則將正在上傳的全部文件進行中斷 Object.keys(reqs).forEach((uid) => { if (reqs[uid]) reqs[uid].abort(); delete reqs[uid]; }); } }
事件觸發(fā)時機圖
總結
以上就是對Upload
組件上傳流程的基本分析。不管是在對組件進行二次開發(fā)或者單獨實現(xiàn)一個上傳組件,希望你能夠?qū)ζ淞鞒涕_發(fā)實現(xiàn)有更好的理解。 如果分析的不是很到位,希望能夠在評論留下你的想法和意見,你的評價和點贊是我學習和輸出的最大動力??
以上就是ElementUI Upload源碼組件上傳流程解析的詳細內(nèi)容,更多關于ElementUI Upload組件上傳的資料請關注腳本之家其它相關文章!
相關文章
在小程序/mpvue中使用flyio發(fā)起網(wǎng)絡請求的方法
這篇文章主要介紹了在小程序/mpvue中使用flyio發(fā)起網(wǎng)絡請求的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09Element中el-select下拉框?qū)崿F(xiàn)選中圖標并回顯圖標
本文主要介紹了Element中el-select下拉框?qū)崿F(xiàn)選中圖標并回顯圖標,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-12-12詳解vue-cli中的ESlint配置文件eslintrc.js
本篇文章主要介紹了vue-cli中的ESlint配置文件eslintrc.js詳解 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09Vuejs對象常用操作之取對應的值、取key和value值、轉(zhuǎn)數(shù)組及合并等
最近在學Vue和javascript感覺js的好多方法都不太清楚,這里徹底總結下,這篇文章主要給大家介紹了關于Vuejs對象常用操作之取對應的值、取key和value值、轉(zhuǎn)數(shù)組及合并等的相關資料,需要的朋友可以參考下2024-01-01vue3.0使用vue-pdf-embed在線預覽pdf 控制頁碼顯示范圍不生效問題解決
這篇文章主要介紹了vue3.0使用vue-pdf-embed在線預覽pdf 控制頁碼顯示范圍不生效問題的問題及解決方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-01-01vue從一個頁面跳轉(zhuǎn)到另一個頁面并攜帶參數(shù)的解決方法
這篇文章主要介紹了vue從一個頁面跳轉(zhuǎn)到另一個頁面并攜帶參數(shù)的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-085個可以加速開發(fā)的VueUse函數(shù)庫(小結)
VueUse為Vue開發(fā)人員提供了大量適用于Vue2和Vue3的基本Composition API 實用程序函數(shù)。具有一定的參考價值,感興趣的可以了解一下2021-11-11