我是如何设计 Upload 上传组件的
Upload 组件设计的目标是解决用户上传文件的便利性,但是中后台 Upload 组件的场景是多种多样的,所以可扩展能力是 Upload 组件不可忽视的另一方面。
同样为了大家能够更加容易的理解,我会从最原始的 input 标签开始说起
<form action="/api/file"><input type="file" /><button type="submit">submit</button>
</form>
这段代码功能: 先选择一个文件,再点提交 POST 一个文件到一个接口。代码虽然不多,但是在实际使用中值得吐槽的点却不少,这里重点说两个点。
- 在每个浏览器上面的表现是各不一样的。
先不说UI不美观,在每个主流浏览器上面的文案基本都不一样,另外在IE下面变化似乎有点大。我们可能的期望是在任何浏览器下交互和UI都一致的组件。
- 文件上传完后页面会刷新带来的体验问题
原生的文件上传都是通过form post 上传,上传完成后整个页面会重定向到 action 的地址。现在大家已经习惯了 ajax 做数据提交,因为可以不需要reload页面就可以带来整个页面的数据更新,无刷新更新的体验会提升很多。
我打算整片拆两个段来讲这个问题,拆分点大约从2012年附近开始,因为 html5 差不多在这个时间段开始被现代浏览器逐步支持。两个段分别叫传统解决方案和现代解决方案
传统解决方案
- UI 一致性问题
我们期望在任何浏览器下都是一个样式,比如一种样式的按钮
<form action="/api/file" method="post"><!-- input 设置为透明,覆盖在 button 上面 ---><input type="file" style="opacity: 0; position:absolute;zindex:9999;top:0;right:0;"/><button type="submit">Upload File</button>
</form>
通过把 input 设置为透明覆盖在 button 按钮上面,让用户以为自己点击的是 button,其实点击的是 button 上面的 input。这样就可以做成用户点击button就能选择文件的“假象”。
查找 button 其实定位到了 input。详细代码可以看这里: https://github.com/alibaba-fu...
- 无刷新上传
我们期望选择完文件立刻执行上传,上传完成后直接在页面上展现上传状态
<iframe name="uploadiframe" style="display:none"></iframe>
<form action="/api/file" method="post" target="uploadiframe"><input type="file" style="opacity: 0; position:absolute;zindex:9999;top:0;right:0;"/><button type="submit">Upload File</button>
</form>
在提交的时候 form 通过 target 指定到对应的 iframe 去上传数据,让form 的数据通过隐藏的 iframe 来提交。
const doc = this.refs.iframe.contentDocument; // 取 iframe
const script = doc.getElementsByTagName('script')[0]; // 清除 iframe 内无用 script
if (script && script.parentNode === doc.body) {doc.body.removeChild(script);
}
const response = JSON.parse(doc.body.innerHTML); // 取返回内容解析成 JSON
因为 iframe 完成上传后页面会整体刷新,再通过监听 iframe 的 onLoad 事件获取返回的结果。关于获取返回内容如何再给主页面做反馈展示的代码可以看这里: https://github.com/alibaba-fu...
现代上传方案
html5 出来后,可以通过 input 可以直接拿到 File 文件对象,再把 File 封装到 FormData,通过 ajax 的形式提交到后端接口实现文件上传。
- UI 一致性问题
不需要再把 input 盖在 button 上面,而是通过监听父节点的点击事件,在事件里面触发 input 的 click 方法。
<script>
function selectFile() {$('#inputfile').click();
}
function onSelect(target) {console.log(target.files); // 获取文件对象
}
</script>
<div role="upload" onclick="selectFile()"><input type="file" style="display: none;" id="inputfile" onchange="onSelect(this)"><button>Upload File</button>
</div>
我其实可以在 div 里面放的不仅仅是 button 了,可以是任何元素,这样我们就能做出任何形状的上传按钮。 下面列举几个例子
卡片状态
<div role="upload"><input type="file" style="display: none;"><div class="selecter"><i class="icon-add" /><span> Upload File </span></div>
</div>
上传面板
<div role="upload"><input type="file" style="display: none;"><div class="selecter"><i class="icon-upload" /><span class="title"> 点击或者拖动文件到虚线框内上传 </span><span class="desc"> 支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型文件 </span></div>
</div>
- 无刷新上传
原理是把 File 对象封装到 FormData,再通过 ajax 的形式提交到后端接口。直接上代码:
function upload(file) {const xhr = new XMLHttpRequest();// 上传进度xhr.upload.onprogress = function progress(e) {};// 上传状态xhr.onload = function onload() {};const formData = new FormData();// 往 formData 里面增加要上传的文件对象formData.append('filename', file);// 指定 api 接口和上传方式xhr.open('POST', '/api/upload', true);// 开始发送数据xhr.send(formData);
}
以上是把一个 file 对象加到 formData 中,再通过 XMLHttpRequest 把 formData 发送到指定的接口 /api/upload 的一个大致过程。详细代码可以查看这里 https://github.com/alibaba-fu...
我们现实中为了可能为了兼容 ie9 , 所以还需要封装一个 uploader,优先支持 html5 但是在 ie9 下自动切换为 iframe 方案。
一个通用的 React 上传组件解决方案
上面我们讲了一个文件上传一定是至少有两步:1. 选择文件 2. 上传文件。并且我们已经有能力根据浏览器自动判断用什么兼容方案。
由此我们做出了两个通用的组件:
- Selecter 文件选择器。可以让任何组件变成一个文件选择器,并且返回选择后的 File 对象
- Uploader 文件上传器。可以像掉 api 一样随心所欲的上传选择的文件,并且可监控进度。
Selecter 文件选择器
封装后的 Selecter 把 input 和相关事件已经处理好了,你只需要关心往里面丢什么
import {Upload, Button} from '@alifd/next';
const Selecter = Upload.Selecter;class App extends React.Comonent {handleSelect = (files) => {// get files}render() {return <Selecter onSelect={this.handleSelect}><Button type="primary">Upload File</Button></Selecter>}
}
如果要换成卡片样式,只要把 children 换掉即可,如下
<Selecter onSelect={this.handleSelect}><Icon type="add" /><span> Upload File </span>
</Selecter>
Uploader 文件上传器
把 Selecter 选择后的File 给 Uploader ,可以很方便的把文件上传到指定接口。
import {Upload, Button} from '@alifd/next';
const Selecter = Upload.Selecter; // 文件选择器
const Uploader = Upload.Uploader; // 文件上传器class App extends React.Comonent {uploader = new Uploader({action: '/api/upload',//onProgress: this.onProgress // 进度监控});handleSelect = (files) => {// 上传文件this.uploader.startUpload(files);}render() {return <Selecter onSelect={this.handleSelect}><Button type="primary">Upload File</Button></Selecter>}
}
因为Selecter的UI可定制,Uploader 的文件上传时机可以随便控制。是的 Selecter 和 Uploader 的组合得以适配任何场景和交互。调试demo 见: https://codepen.io/frankqian/...
比如我们可以通过 Uploader 自定义各种功能,比如做一个 粘贴上传组件
去除用来装饰的进度条,不到20行代码就写完了整个组件:
import { Upload, Input } from '@alifd/next';const Uploader = Upload.Uploader; // 文件上传器class App extends React.Component {uploader = new Uploader({action: '/api/upload',});// 处理粘贴事件onPaste = e => {const files = e.clipboardData.files; // 获取粘贴的文件数据this.uploader.startUpload(files); // 上传文件};render() {return <Input.TextArea onPaste={this.onPaste} placeholder="粘贴截图到这里" />;}
}
可以在这里调试代码:https://codepen.io/frankqian/...
进一步提取更通用的使用方式
解决易用性的问题
Selecter 和 Uploader 使用起来虽然非常灵活,但是还是要自己写一些逻辑,把取到的 File 对象和 Uploader 做上传关联。而我们在文件上传最常用的交互方式是选择完就开始上传、上传完成后给反馈。所以我们把常见的交互进一步做提取,单按钮、卡片、拖拽面板 等,主要把常用UI和上传交互沉淀下来,方便大多的场景使用。
import {Upload, Button} from '@alifd/next';class App extends React.Comonent {handleChange = (file) => {console.log(file.url); // 直接获取图片 url}render() {return <div><Upload action="/api/file" onChange={this.handleChange}><Button type="primary">Upload File</Button></Upload><Upload action="/api/file" shape="card" onChange={this.handleChange}>Upload File</Upload><Upload.Dragger action="/api/file" onChange={this.handleChange}/></div>}
}
以上就结合业务线常用的上传方案和交互提取的上传方式,我们把 Selecter 和 Uploader 进行进一步封装,得到一个UI和交互相对固定的组件,使用起来更便捷。
阿里内部各个业务线上传的需求是多种多样的,Fusion Next 的 Upload 组件要考虑效率和能力之前的平衡。一个好的组件应该通过固定组件去解决 80% 的通用问题;剩下的 20% 可能各业务线不一样,可以通过扩展能力让各业务线去支持。
相关链接
Fusion Upload: https://fusion.design/compone...
github: https://github.com/alibaba-fu...
我是如何设计 Upload 上传组件的相关推荐
- Element中Upload上传组件的http-request方法
刚开始接触vue,新框架是vue+Element.其中写到上传组件的时候卡住了.问题详细: Upload上传组件中的action是必选参数,但是调后台接口的时候需要手动加参数和token,比较麻烦. ...
- 页面中使用多个element-ui upload上传组件时绑定对应元素
elemet-ui里提供的upload文件上传组件,功能很强大,能满足单独使用的需求,但是有时候会存在多次复用上传组件的需求,如下图的样子,这时候就出现了问题,页面上有多个上传组件时,要怎么操作呢? ...
- 前端学习(2011)vue之电商管理系统电商系统之初步使用upload上传组件
目录结构 router.js import Vue from 'vue' import Router from 'vue-router' import Login from './components ...
- element-ui upload 上传组件附带额外参数进行上传(表单形式,多个参数)
之前一直使用upload组件单个上传文件,最近遇到需要上传表单字段,表单中有多个参数 下图是接口要求: 官网上传组件中提供了响应的功能实现,但是demo中未演示,不注意看文档参数的话,可能会不知道这个 ...
- LayUI upload上传组件上传文件的两种方式(手动上传、自动上传)
1 手动上传 上传文件分为两步,第一步选择文件,第二步上传文件. HTML代码: <input type='button' id='selectFile' value='选择文件'> &l ...
- uniapp - 全平台兼容的 “多图上传“ 功能,搭配 uview 组件库中的 upload 上传组件(附带详细的示例源码及注释,可直接复制使用或简单修改)
效果图 使用 uniapp 开发,多平台全端兼容的多图上传功能,支持限制个数及移除等. 组件库使用的是 uview 框架,上传组件基于 Upload组件,功能完美无bug. 准备阶段 Upload组件 ...
- 前端学习(2695):重读vue电商网站16之Upload 上传组件
通过点击或者拖拽上传文件 Js <!-- action表示图片上传后台api地址 --> <el-upload:action="uploadURL":on-pre ...
- vue使用iview中Upload上传组件
先上代码,注解在下面 html代码 <template> <Uploadmultiple action="Api/api/sys/fileController/upload ...
- Antd 的 Upload 上传组件 uploading 状态踩坑记
说明:在使用Antd 的 Upload 组件 的onChange()方法中,打印fileList 中的文件状态status 一直是 uploading,无法拿到上传文件后服务端响应的内容,且组件下方不 ...
最新文章
- 职业-把工作当作职业 or 事业?
- 51.1AP!单阶段检测器的新纪录,TOOD:即插即用的检测器换头术,显著提升性能
- 工信部明确公共互联网网络安全突发事件分级预警、应急
- geek软件_社团秀@UNC新媒体协会@管理会计研学社@Geek社团
- QT的QGeoRoutingManager类的使用
- 新手坐高铁怎么找车厢_京沪高铁设置静音车厢,你怎么看?
- linux7 修改服务启动项目命令,centos7服务部署flask项目
- 0x29——如何把自己iphone app传到iphone上
- 科研|青椒工作九年后感慨:比SCI重要,比项目值钱的是…
- ITU-T Technical Paper: QoS的构建模块与机制
- 中科大自主招生2018年笔试数学之四
- 互联网进化论在中国科技论文在线正式发表
- [轻松学会shell编程]-3、grep、正则表达式、awk的详细用法、分析系统自带的两个文件(functions和network)
- Unity 编辑器下运行没有声音
- android TVBOX OTT IPTV
- 小米手环3 NFC 自定义 门禁卡数据
- 2020年非上海生源应届普通高校毕业生落户材料办理流程及注意事项
- python3.5中import cv2报错
- 计算机科学与技术与光电,光电信息科学与工程考研科目有哪些?
- 新产品开发的项目管理