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

你可能感兴趣的:(react 文件上传组件的简单封装)