react 檔案上傳元件的簡單封裝
背景:一個老的專案需要增加上傳excel檔案的功能。
需求:實現一個類似於antd元件庫中Upload元件的元件
<Upload action="url">
<Button>
<Icon type="upload" /> Upload Directory
</Button>
</Upload>,
基本思路:
1.使用HTML5 的 input type="file"來進行檔案的匯入
2.檔案流的傳輸跟我們常見的字串傳輸不一樣,簡單的ajax肯定不行。因此選用了 XMLHttpRequest Level 2 新增的FormDate物件,它除了常規的表單提交之外,還可以進行二進位制檔案的上傳。
我試著修改了value也不行,然後好像也沒發現有什麼屬性可以解決。那麼我放棄直接去修改input元素本身了,可以把input元素給隱藏起來,然後使用我自定義botton來展現。
4.設計元件。我要實現的就像上面的antd中的Upload元件那樣,能夠把button包住,點選botton然後觸發一系列的開啟資料夾、上傳等動作。我先把元件命名為DjUploader,那麼我的元件也應該這麼用
<DjUploader ...>
...
</DjUploader>
5.編寫元件。元件內部需要實現的主要部分:
- 隱藏原生的input
- 通過元件的子元件的onClick來觸發input的click,來開啟資料夾
- 匯入檔案之後,也就是input 的onChange事件,使用FormDate來處理匯入的檔案
- ajax發post請求帶上FormDate
隱藏原生的input:就是弄一下css,使用visibility: hidden或opacity: 0都行,別display:none就行了。
通過元件的方法來觸發的input的click:那就先使用React.createRef()引用input的例項,再手動input click()就行了。關鍵在於通過其子元件的onClick來觸發input click(),平時我們說子元件向父元件通訊,父元件將自己的方法傳遞給子元件就行了,但這是載子元件顯式存在的情況下。
render(){
return<>{this.props.children}</>
}
這種情況下是沒辦法通過像在寫jsx是通過onClick屬性來繫結事件的,這個時候肯定要通過別的方式了。
這個方式就是React.cloneElement()。
相比之下,我相信大傢伙會更熟悉React.createElement(),不過平時專案裡都寫jsx,應該沒人真用React.createElement來建立react元素吧(手動狗頭)。
不妨複習一下React.createElement(),它接收三個引數,形如:
React.createElement(
type,
[props],
[...children]
)
type: 原生dom 的tagName,例如div、span、a等等或者React 的元件
[props]:所有屬性
[…children]:所有的子元件
React.cloneElement():顧名思義,克隆,它就是克隆出一個react元素出來
React.cloneElement(
element,
[props],
[...children]
)
element:要克隆的目標,它就是一個react元素
[props]:新新增的屬性,會合併到克隆來的屬性陣列中
[…children]:新新增的子元件,注意,新增的子元件不是合併,而是替換
回到上面我們解決this.props.children的形式子元件事件觸發父元件方法,那麼就可以使用React.cloneElement(child,{onClick:xxx}),先克隆一下元件例項化時傳入的子元件,然後給其新增onClick屬性。
FormDate處理匯入的檔案:
input上的.files獲取檔案物件,FormDate.append(‘key’,files)。
FormDate的append方法新增鍵值對,值就是檔案物件了
ajax:data:formData
附加:input 也可以設定檔案型別啊多個檔案上傳啊等,這裡不做贅述了
最後貼下元件的程式碼
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./djUploader.scss";
interface Props {
action: string;
accept: string;
}
export class DjUploader extends React.Component<Props, any> {
private ref: any;
constructor(props) {
super(props);
this.ref = React.createRef();
}
render() {
return (
<a className="dj-upload-container" type="button">
<input
type="file"
id="uploadFile"
className="dj-upload"
accept={this.props.accept}
ref={this.ref}
onChange={this.upload}
/>
{/* 要給子元件繫結事件,要先克隆一下子元件,再將事件傳入克隆的元件的屬性物件中 */}
{React.Children.map(this.props.children, (child: any) => {
return React.cloneElement(child, { onClick: this.chooseFile });
})}
</a>
);
}
chooseFile = () => {
this.ref.current.click();
};
upload = () => {
//使用formDate物件提交
let formDate = new FormData();
formDate.append(this.ref.current.files[0].name, this.ref.current.files[0]);
debugger;
$.ajax(this.props.action, {
method: "POST",
data: formDate,
async: true,
cache: false,
contentType: false,
processData: false,
success: (res) => {
alert("上傳成功");
},
error: (res) => {
// console.log("上傳失敗", res);
alert("格式錯誤");
},
});
};
}