背景:一个老的项目需要增加上传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.编写组件。组件内部需要实现的主要部分:
隐藏原生的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("格式错误");
},
});
};
}