1. 程式人生 > 其它 >react 檔案上傳元件的簡單封裝

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物件,它除了常規的表單提交之外,還可以進行二進位制檔案的上傳。

3.input 預設樣式的修改。在input 設定type=“file” 後會預設為下圖的樣式
在這裡插入圖片描述
我試著修改了value也不行,然後好像也沒發現有什麼屬性可以解決。那麼我放棄直接去修改input元素本身了,可以把input元素給隱藏起來,然後使用我自定義botton來展現。
4.設計元件。我要實現的就像上面的antd中的Upload元件那樣,能夠把button包住,點選botton然後觸發一系列的開啟資料夾、上傳等動作。我先把元件命名為DjUploader,那麼我的元件也應該這麼用

<DjUploader ...>
	...
</DjUploader>

5.編寫元件。元件內部需要實現的主要部分:

  1. 隱藏原生的input
  2. 通過元件的子元件的onClick來觸發input的click,來開啟資料夾
  3. 匯入檔案之後,也就是input 的onChange事件,使用FormDate來處理匯入的檔案
  4. ajax發post請求帶上FormDate

隱藏原生的input:就是弄一下css,使用visibility: hidden或opacity: 0都行,別display:none就行了。

通過元件的方法來觸發的input的click:那就先使用React.createRef()引用input的例項,再手動input click()就行了。關鍵在於通過其子元件的onClick來觸發input click(),平時我們說子元件向父元件通訊,父元件將自己的方法傳遞給子元件就行了,但這是載子元件顯式存在的情況下。

現在需要是實現this.props.children這種形式下的子元件事件觸發父元件方法

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("格式錯誤");
      },
    });
  };
}