我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
Consic是一个简单易用轻量级React组件库
本文记录了我如何实现Upload组件
附上组件源码链接!!!
写在前面(小声)
准大三菜鸟一枚,文章记录了我在封装Upload组件时的一些想法以及思考,如果代码有什么不好的地方希望能够帮忙指出(一定会好好回复!)👂
组件展示(duang duang duang)
支持的API列表!
Name | Description | Type | Default |
---|---|---|---|
accept | 允许的文件类型,参考MDN(accept)属性 | string | * |
multiple | 是否支持多选 | boolean | false |
limit | 限制上传文件的数量 | number | 无限制 |
disable | 是否禁用上传 | boolean | false |
name | 上传文件的键名 | string | file |
action | 远程上传地址 | string | 无 |
autoUpload | 是否自动上传 | boolean | true |
withCredentials | 是否携带cookie上传 | boolean | false |
defaultFileList | 默认文件列表 | FileItem[] | [] |
headers | 发送请求时自定义请求头 | HeadersInit | {} |
beforeUpload | 上传前回调函数 | (file: File) => Awaitable<boolean | void> | noop |
onRemove | 移除后回调函数 | (fileItem: FileItem, fileList: FileItem[]) => void | noop |
onChange | 文件列表变动时回调函数 | (fileList: FileItem[]) => void | noop |
onSuccess | 上传成功回调函数 | (response: any, fileItem: FileItem, fileList: FileItem[]) => void | noop |
onError | 上传失败回调函数 | (error: any, fileItem: FileItem, fileList: FileItem[]) => void | noop |
onExceedLimit | 超出限制上传数量时回调 | (fileList: FileItem[], uploadFiles: FileList) => void | noop |
开始我们的码代码之旅!
静态结构展示
Upload
组件的结构比较简单(目前),因为还没做上传图片时的特殊展示(肯定不是因为我懒!),结构部分概括就是div
包裹住用于可以触发上传文件的input
标签(需要隐藏,然后使用click()
触发上传事件)以及用于点击的Button
按钮和展示上传文件的List
组件,具体代码如下:
<div>
<input
accept={accept}
multiple={multiple}
onChange={fileChange}
type="file"
ref={inputRef}
style={{ display: 'none' }}
></input>
<Button handleClick={uploadClick} style={{ margin: '8px' }} type="primary">
<UploadOutlined />
上传
</Button>
{fileList.length !== 0 && (
<List
style={{ fontSize: '14px', width: '100%', marginTop: '10px' }}
header="文件列表"
size="small"
dataSource={fileList}
render={(_: FileItem, idx: number) => {
return (
<List.Item
key={idx}
style={{ fontSize: '14px', display: 'flex', justifyContent: 'space-between'}}>
<span style={{ color: _.status === 'success' ? '#00b42a' : '#d81f27' }}>
{_.name}
</span>
<span>
{_.status === 'unUpload' ? (
<Button
handleClick={() => uploadFile(_.file, _)}
style={{ height: '100%', fontSize: '12px', }}
> 上传 </Button>
) : ( <CheckCircleFilled style={{ color: '#00b42a' }} / )}
<CloseCircleFilled
onClick={(e) => { e.stopPropagation(); deleteFile(_); }}
style={{ color: '#d81f27', marginLeft: '5px' }}
/>
</span>
</List.Item>
);
}}
></List>
)}
</div>
功能代码
Upload
组件结构相对比较简单,还是快点开始具体的代码实现吧嘿嘿,我会按上传流程来讲解一个个函数各自的功能
uploadClick()函数
该函数的用于点击上传按钮后,通过inputRef
绑定的DOM
触发上传事件,在触发前会对disable
进行判断。下面贴代码:
const uploadClick = () => {
if (disable) return;
inputRef.current.click();
};
fileChange()函数
当选择完文件并确定后,触发input
绑定的onChange
事件,选择的文件通过e.target.files
拿到,对文件数组进行遍历, limit
传参时,当上传文件总数大于等于limit
,触发自定义事件onExceedLimit
并跳出循环,在循环中,将File
包裹,并添加uid(通过web自带函数 crypto.randomUUID()生成)|status
等属性,如果用户设置了autoUpload
,再此刻就会触发文件上传。代码如下:
const fileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files || [];
const list: FileItem[] = [];
for (let i = 0; i < files.length; i++) {
if (isNumber(limit) && fileList.length + list.length >= limit) {
onExceedLimit && onExceedLimit(fileList, files as FileList);
break;
}
list.push({
uid: crypto.randomUUID(),
status: 'unUpload',
name: files[i].name,
file: files[i],
});
autoUpload && (await uploadFile(files[i], list[list.length - 1]));
}
inputRef.current.value = '';
setFileList([...fileList, ...list]);
};
beforeUploadFile()函数
假设上面中autoUpload=true
,进入上传文件函数uploadFile
,函数首行调用beforeUploadFile
,函数支持用户返回Promise的reject以及false
,如果这样,则不会上传文件。具体代码如下:
const beforeUploadFile = async (file: File): Promise<boolean> => {
if (beforeUpload) {
try {
const res = await beforeUpload(file);
return !(typeof res === 'boolean' && !res);
} catch (e) {
return false;
}
} else {
return true;
}
};
uploadFile()函数
函数参数是目标文件,以及FileItem
,在这里我选择浏览器环境自带的fetch进行文件上传,fetch
配置项包括,上传地址action
,是否携带cookiewithCredentials
,请求头自定义headers
,请求回调中,then
中,更改上传成功的文件status
为success
,然后调用用户的自定义函数onSuccess
,catch
中,更改上传成功的文件status
为error
,然后调用用户的自定义函数onError
。具体代码如下:
const uploadFile = async (file: File, fileItem: FileItem) => {
if (!(await beforeUploadFile(file))) return;
const formData = new FormData();
formData.append(name, file);
fetch(action, {
method: 'POST',
body: formData,
credentials: withCredentials ? 'include' : 'omit',
headers,
})
.then((response) => response)
.then((data) => {
setFileList((fileList) => {
return fileList.map((_) => {
if (_.uid === fileItem.uid) {
return { ..._,status: 'success',};
} else return _;
});
});
onSuccess && onSuccess(data, fileItem, fileList);
})
.catch((error) => {
setFileList((fileList) => {
return fileList.map((_) => {
if (_.uid === fileItem.uid) {
return { ..._,status: 'error',};
} else return _;
});
});
onError && onError(error, fileItem, fileList);
});
};
deleteFile()函数
const deleteFile = (fileItem: FileItem) => {
setFileList((fileList) => fileList.filter((_) => _.uid !== fileItem.uid));
onRemove && onRemove(fileItem, fileList);
};
终
FLAG
- 支持图片文件的展示
- 图片可预览
- 图片上传前可裁剪
- 支持
List
的自定义render
这是我在掘金更新的有关组件的第三篇文章了,我知道代码中有挺多不规范和不合理的地方,希望大家可以多多指出!!