實現一個File-Reader元件用來讀取本地資源。
概述: 在使用者手動上傳一些資源的時候,需要分為兩步,第一步是將其從本地讀取出來,得到一個file
物件,然後再上傳至伺服器。該元件用於第一步,然後可通過後續進一步封裝程Upload元件。
- 新增拖拽上傳的功能;
- 本地檔案的批量讀取,以及相關預覽;
- 相關瀏覽器的預設行為的
preventDefault
; - 相關檔案的基本校驗。
2019年1月1日更新
在該元件的 destroyed
生命週期中新增新特性,利用 URL.revokeObjectURL()
靜態方法用來釋放一個之前通過呼叫 URL.createObjectURL()
建立的已經存在的 URL 物件,從而進行垃圾收集。
destroyed() {
if (this.targetFiles.length && window.URL.revokeObjectURL) {
this.targetFiles.forEach(file => {
window.URL.revokeObjectURL(file.url);
});
}
}
複製程式碼
1. 例項
程式碼
<!-- 基礎用法 -->
<fat-filereader
accept=".png, .jpg"
:size="500 * 1024"
@success="event => readHandler(event, 'file')"
@error="errorHandler"
/>
<!-- 自定義上傳區域 -->
<fat-filereader @success="event => readHandler(event, 'otherFile')">
<div slot="clickarea" class="upload-area">
<fat-icon class="icon" name="cloud_upload" size="24"/>
</div>
</fat-filereader>
<!-- 拖拽上傳 -->
<fat-filereader dragable @success="event => readHandler(event, 'anotherFile')">
<div slot="clickarea" class="upload-area">
<fat-icon class="icon" name="cloud_upload" size="24"/>
</div>
<span v-if="anotherFile.name">已上傳:{{ anotherFile.name }}</span>
</fat-filereader>
<!-- 上傳多個檔案 -->
<fat-filereader multiple :limit="5" @success="multipleHandler" @error="errorHandler"/>
複製程式碼
例項地址:File-Reader 例項
程式碼地址:Github UI-Library
2. 原理
該元件的實現是基於原生的<input type="file" />
,再通過新增拖拽功能來以達到上圖效果。
元件的基本結構如下,主要包含兩個部分:
- 其一是
<slot name="clickarea"></slot>
自定義的點選區域; - 其二是原生的
< input type="file" />
用來完成檔案上傳工作。
<div
:class="['file-reader', { 'is-disabled': disabled }]"
>
<div
class="click-area"
@click.stop="eventHandler('readFile')"
@dragenter="prevent"
@dragover="prevent"
@drop="event => dragable && eventHandler('dropReadFile', event)"
>
<slot name="clickarea">
<fat-button :disabled="disabled" type="success">上傳</fat-button>
</slot>
</div>
<slot></slot>
<input
ref="input"
type="file"
class="is-hide"
v-bind="$attrs"
@change="event => eventHandler('change', event)"
:disabled="disabled"
>
</div>
</template>
複製程式碼
在<slot name="clickarea"></slot>
外層包裹div
標籤,用於監聽該區域的點選事件。當它觸發時,相關處理函式為
const handler = {
readFile: () => {
// fix change again
this.$refs.input.value = "";
this.$refs.input.click();
},
...
};
複製程式碼
先重置this.$refs.input.value
,不然會出現無法再上傳的問題。然後,觸發原生<input type="file" />
的點選事件,此時頁面上會彈出原生的上傳框。
監聽該標籤的change
事件,
@change="event => eventHandler('change', event)
,如果發生上傳,會觸發相關處理函式。
eventHandler(type, event = {}) {
const handler = {
...
// read files
change: event => (this.sourceFiles = event.target.files)
};
handler[type] && handler[type](event);
}
複製程式碼
從event
物件中,讀取選中待上傳的檔案物件
包含著name
檔名,size
大小,type
型別等屬性。
由於部分檔案需要進行預覽,例如img、svg等,所以需要生成對應的URL,基本方法為:
window.URL.createObjectURL
:該方法會建立一個 DOMString,其中包含一個表示引數中給出的物件的URL;new FileReader
:該物件允許Web應用程式非同步讀取儲存在使用者計算機上的檔案(或原始資料緩衝區)的內容,再通過readAsDataURL
讀取指定的 Blob 或 File 物件。
首先判斷當前是否支援window.URL && window.URL.createObjectURL
,如果支援,只需將已上傳的targetFiles
進行處理
Array.prototype.map.call(value, file => ({
// file => url
url: window.URL.createObjectURL(file),
file
}));
複製程式碼
如果不支援該方法,則需要利用File Reader
的readAsDataURL
,由於該方法是非同步的,所以將其封裝為Promise形式
const getReader = file => {
return new Promise(function(resolve, reject) {
const fileReader = new FileReader();
// 讀取相關檔案
fileReader.readAsDataURL(file);
// fileReader onload 時,返回其結果
fileReader.onload = () => resolve(fileReader.result);
fileReader.onerror = () => fileReader.abort();
});
};
const readers = Array.prototype.map.call(value, file => getReader(file));
複製程式碼
再利用Promise.all
統一進行處理,結合已上傳的targetFiles
生成返回結果。
Promise.all(readers).then(results => {
this.targetFiles = results.map((url, i) => ({
url,
file: value[i]
}));
});
複製程式碼
以上完成基本的上傳、預覽功能。然後新增拖拽上傳功能,在clickArea監聽dragenter
、dragover
、drop
事件。
<div
class="click-area"
@click.stop="eventHandler('readFile')"
@dragenter="prevent"
@dragover="prevent"
@drop="event => dragable && eventHandler('dropReadFile', event)"
>
<slot name="clickarea">
<fat-button :disabled="disabled" type="success">上傳</fat-button>
</slot>
</div>
複製程式碼
相關處理函式為
eventHandler(type, event = {}) {
const handler = {
...
dropReadFile: event => {
event.preventDefault();
this.sourceFiles = event.dataTransfer.files;
}
};
handler[type] && handler[type](event);
},
prevent(event) {
event.preventDefault();
}
複製程式碼
利用event.dataTransfer.files
獲取相關上傳檔案,再通過event.preventDefault
阻止遊覽器的一些預設行為,例如直接預覽該檔案等。獲取到sourceFiles
之後的操作等同於之前點選上傳。
讀取完成之後需要對檔案進行校驗,主要有上傳檔案的大小以及數量,相關程式碼如下
targetFiles(value) {
const { size, limit } = this;
if (value.length > limit) {
this.$emit("error", {
msg: "the quantity of files is too large its number cannot exceed"
});
} else {
if (
value.some(item => {
const {
file: { size: fileSize }
} = item;
return size && fileSize > size;
})
) {
this.$emit("error", {
msg: "file is too large its size cannot exceed"
});
} else {
this.$emit("success", value);
}
}
}
}
複製程式碼
3. 使用
相關的原生<input type="file" />
的accept
,multiple
等屬性,利用v-bind=$attrs
傳遞給原生的<input type="file" />
。
之後, 可以結合相關上傳方法將其封裝程Upload元件,以Axios為例
uploadFile (fileData) {
const config = {
// 依據當前環境配置
baseURL: ...,
headers: { 'Content-type': 'multipart/form-data' }
}
let data = new FormData();
data.append('file', fileData);
data.append('name', fileData.name);
return Axios.post('upload url', data, config)
}
複製程式碼
4. 總結
封裝一個File-reader元件,簡化其內部邏輯,方便後續擴充。
往期文章:
- 從零實現Vue的元件庫(零)- 基本結構以及構建工具
- 從零實現Vue的元件庫(一)- Toast 實現
- 從零實現Vue的元件庫(二)- Slider 實現
- 從零實現Vue的元件庫(三)- Tabs 實現
- 從零實現Vue的元件庫(四)- File-Reader 實現
- 從零實現Vue的元件庫(五)- Breadcrumb 實現
- 從零實現Vue的元件庫(六)- Hover-Tip 實現
- 從零實現Vue的元件庫(七)- Message-Box 實現
- 從零實現Vue的元件庫(八)- Input 實現
- 從零實現Vue的元件庫(九)- InputNumber 實現
- 從零實現Vue的元件庫(十)- Select 實現
- 從零實現Vue的元件庫(十一)- Date-picker 實現
- 從零實現Vue的元件庫(十二)- Table 實現
- 從零實現Vue的元件庫(十三)- Pagination 實現
- 從零實現Vue的元件庫(十四)- RadioGroup 實現
- 從零實現Vue的元件庫(十五)- CheckboxGroup 實現
原創宣告: 該文章為原創文章,轉載請註明出處。