上傳檔案也是我們在實際開發中常遇到的功能,比如上傳產品圖片以供更好地宣傳我們的產品,上傳excel文件以便於更好地展示更多的產品資訊,上傳zip檔案以便於更好地收集一些資料資訊等等。至於為何要把上傳元件封裝成一個公共的、可複用的元件,在前兩篇文章封裝react antd的form表單元件、封裝react antd的表格table元件中已經作了很多介紹,這裡同樣不再贅述。
有朋友覺得這些功能元件在各種前端框架滿天飛的今天都大同小異、大差不差,甚至覺得Level有點Low。哈哈,其實事實也確實是這樣的,我們去二次封裝這些原本已基本成熟的框架元件,逼格也確實不高,且隨心所欲的空間也不大,但我覺得這並不妨礙我們的日常開發。我們作為前端er,大部分工作都是在產品er的需求陰影下,頂著髮際線越來越高的蒙圈腦袋,認認真真的砌牆,在膽戰心驚、如履薄冰地試探“今晚會否加班”的心態中活過每一個白天,已經是阿彌陀佛了。如果再碰上一個好的領導,在每一個階段的開發任務結束後能再請我們去嗨皮一番,那就真是“今天好天氣,老狼請吃雞了。” 所以,我們能做的,只有簡化開發步驟,提高元件的複用率,再完美一點就是提供可定製化的封裝元件了。但這是個仁者見仁智者見智的看法,我還是很尊重認可有獨立思考能力的人!
話不多說,先來介紹一下封裝過程中所關注的幾個點:
- 上傳檔案的大小
上傳檔案勢必需要我們去關注所上傳檔案的大小。這個功能點,我們放在antd所提供的upload元件的beforeUpload方法中,當然beforeUpload方法我們也是封裝在upload元件中,通過使用時傳入的配置來控制檔案大小。
- 上傳檔案的格式或型別
上傳檔案的格式或型別也是我們必須要去關注的一個點,比如這個上傳控制元件只能上傳圖片,那個上傳控制元件只能上傳excel文件等等,我們可以使用antd所提供的upload元件的accept來控制,當然accept也是被封裝在了upload元件中。
- 自定義上傳的方法
antd所提供的upload元件中給我們提供了一個action的API,官方的解釋是action:上傳的地址,跟form的表單提交有點類似。這種的上傳方式我們不太好控制,我們會使用upload元件的customRequest方法來自定義上傳的實現,官方對其的解釋是:通過覆蓋預設的上傳行為,可以自定義自己的上傳實現。
關於上傳功能,我們需要注意的也基本就是以上三點,至於我們是上傳到自己的伺服器還是第三方如阿里雲,則不在這篇文章的介紹範圍了。下面來看一下具體實現。
外甥打燈籠——照舅(照舊)先放一張效果圖:
效果圖有點小,因為是縮圖,就不要介意這個細節了。
1、所封裝的上傳元件upload.js
import { createElement } from 'react'
import { Upload, message } from 'antd';
const h = createElement;
const SUFFIX = /.+(\.\w+)$/,
BYTE = 1024,
ACCEPT = {
zip: 'application/zip,application/x-zip,application/x-zip-compressed',
pdf: 'application/pdf',
excel: 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
image: 'image/jpeg,image/bmp,image/png,image/gif',
},
getAccepts = accept => (Array.isArray(accept) ? accept : [accept]).map(ac => ACCEPT[ac]).join(','),
beforeCheck = (config, file) => {
let { accept, max = Number.MAX_VALUE } = config || {}, { size, type } = file, accepts = getAccepts(accept).split(',');
//大小限制(M)
if(Math.pow(BYTE, 2) * max < size){
message.info(`檔案不能超過${max}M`);
return false;
}
}
const UploadComponent = props => {
let { children, config } = props, { accept } = config, attrs = {};
//不能在props物件上直接新增屬性,只能再定義一個attrs物件
Object.assign(attrs, {
action: '',
accept: getAccepts(accept),
beforeUpload: file => beforeCheck(config, file),
customRequest: opts => {
let { file, onSuccess, onProgress, onError } = opts, { uid, name, type } = file;
name = `${uid}${name.replace(SUFFIX, '$1')}`;
//判斷上傳的檔案是否是圖片,若不是圖片,前端可自行根據isImg來控制是否可預覽檔案
if(getAccepts(accept).indexOf(type) > -1) file.isImg = true
// fetch('https://jsonplaceholder.typicode.com/posts', {
// method: 'POST',
// body: file,
// }).then(r => {
// let { res: { requestUrls } } = r;
// requestUrls = requestUrls.length < 1 ? '' : requestUrls[0]
// if (requestUrls.indexOf('?') > -1) requestUrls = requestUrls.split('?')[0]
// onSuccess({ res: file, url: requestUrls });
// })
let resFile = {
uid,
name,
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}
onSuccess(resFile)
},
})
return h(Upload, {...props, ...attrs}, children)
}
export default UploadComponent
程式碼中註釋的部分模擬的就是上傳到伺服器,不過這個地址是從antd上抄來的,貌似不能用。
2、upload元件的使用方法:
import React, { useState } from 'react'
import Upload from './Upload'
const UploadComp = () => {
const config = {
accept: 'image', //接受上傳的檔案型別:zip、pdf、excel、image,也可以是檔案型別所組成的陣列型別如:['image', 'pdf'],則只可以上傳圖片或pdf型別的檔案,也可以為空,則任何型別的檔案都可以上傳
max: 1, //限制上傳檔案大小
};
const [fileList, setFileList] = useState([])
const onPreview = file => {
console.log(file)
}
const onSuccess = res => {
//useState不能向陣列中push資料,只能通過這樣的方式來push資料
setFileList([...fileList, res])
}
return <Upload listType="picture-card" config={config} fileList={fileList} onSuccess={onSuccess} onPreview={onPreview}>{fileList.length >= 2 ? null : '上傳'}</Upload>
}
export default UploadComp
對於封裝的這個upload元件,還有一點想說的就是我只是按照antd的API做了封裝,然後再把這些API傳遞給upload元件。如果你說我想把上傳兩個字給成按鈕形式的可以嗎?當然可以,只要antd是如何做到的,我這裡就也是如何做到的。所以參照antd的官方實現方法即可。
最後還是再貼一下本次封裝所用到的各個包的版本:
react: 16.8.6,
react-dom: 16.8.6,
react-router-dom: 5.0.0,
antd: 4.3.5,
@babel/core: 7.4.4,
babel-loader: 8.0.5