Upload 组件设计的目标是解决用户上传文件的便利性,但是中后台 Upload 组件的场景是多种多样的,所以可扩展能力是 Upload 组件不可忽视的另一方面。

同样为了大家能够更加容易的理解,我会从最原始的 input 标签开始说起

<form action="/api/file"><input type="file" /><button type="submit">submit</button>
</form>

这段代码功能: 先选择一个文件,再点提交 POST 一个文件到一个接口。代码虽然不多,但是在实际使用中值得吐槽的点却不少,这里重点说两个点。

  • 在每个浏览器上面的表现是各不一样的。

1550121699861-f22892d5-b799-47ab-a8d6-8bb64a42e829.png

先不说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就能选择文件的“假象”。

1550129218663-bb81f35f-ec5e-4411-93ab-e2f236ff008c.png

查找 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 和相关事件已经处理好了,你只需要关心往里面丢什么

1550121699895-0f58240e-fc8b-49df-b315-056688969720.png

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 换掉即可,如下

1550121699899-0de11939-68db-428e-9917-563f8764ebfd.png

<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 自定义各种功能,比如做一个 粘贴上传组件

1550131189249-e1193c86-4419-4e3d-b83b-232068e9bf36.png

去除用来装饰的进度条,不到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和上传交互沉淀下来,方便大多的场景使用。

1550123392700-a241a107-d6a2-4fa5-b3e3-566f73695b5c.png

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 上传组件的相关推荐

  1. Element中Upload上传组件的http-request方法

    刚开始接触vue,新框架是vue+Element.其中写到上传组件的时候卡住了.问题详细: Upload上传组件中的action是必选参数,但是调后台接口的时候需要手动加参数和token,比较麻烦. ...

  2. 页面中使用多个element-ui upload上传组件时绑定对应元素

    elemet-ui里提供的upload文件上传组件,功能很强大,能满足单独使用的需求,但是有时候会存在多次复用上传组件的需求,如下图的样子,这时候就出现了问题,页面上有多个上传组件时,要怎么操作呢? ...

  3. 前端学习(2011)vue之电商管理系统电商系统之初步使用upload上传组件

    目录结构 router.js import Vue from 'vue' import Router from 'vue-router' import Login from './components ...

  4. element-ui upload 上传组件附带额外参数进行上传(表单形式,多个参数)

    之前一直使用upload组件单个上传文件,最近遇到需要上传表单字段,表单中有多个参数 下图是接口要求: 官网上传组件中提供了响应的功能实现,但是demo中未演示,不注意看文档参数的话,可能会不知道这个 ...

  5. LayUI upload上传组件上传文件的两种方式(手动上传、自动上传)

    1 手动上传 上传文件分为两步,第一步选择文件,第二步上传文件. HTML代码: <input type='button' id='selectFile' value='选择文件'> &l ...

  6. uniapp - 全平台兼容的 “多图上传“ 功能,搭配 uview 组件库中的 upload 上传组件(附带详细的示例源码及注释,可直接复制使用或简单修改)

    效果图 使用 uniapp 开发,多平台全端兼容的多图上传功能,支持限制个数及移除等. 组件库使用的是 uview 框架,上传组件基于 Upload组件,功能完美无bug. 准备阶段 Upload组件 ...

  7. 前端学习(2695):重读vue电商网站16之Upload 上传组件

    通过点击或者拖拽上传文件 Js <!-- action表示图片上传后台api地址 --> <el-upload:action="uploadURL":on-pre ...

  8. vue使用iview中Upload上传组件

    先上代码,注解在下面 html代码 <template> <Uploadmultiple action="Api/api/sys/fileController/upload ...

  9. Antd 的 Upload 上传组件 uploading 状态踩坑记

    说明:在使用Antd 的 Upload 组件 的onChange()方法中,打印fileList 中的文件状态status 一直是 uploading,无法拿到上传文件后服务端响应的内容,且组件下方不 ...

最新文章

  1. 职业-把工作当作职业 or 事业?
  2. 51.1AP!单阶段检测器的新纪录,TOOD:即插即用的检测器换头术,显著提升性能
  3. 工信部明确公共互联网网络安全突发事件分级预警、应急
  4. geek软件_社团秀@UNC新媒体协会@管理会计研学社@Geek社团
  5. QT的QGeoRoutingManager类的使用
  6. 新手坐高铁怎么找车厢_京沪高铁设置静音车厢,你怎么看?
  7. linux7 修改服务启动项目命令,centos7服务部署flask项目
  8. 0x29——如何把自己iphone app传到iphone上
  9. 科研|青椒工作九年后感慨:比SCI重要,比项目值钱的是…
  10. ITU-T Technical Paper: QoS的构建模块与机制
  11. 中科大自主招生2018年笔试数学之四
  12. 互联网进化论在中国科技论文在线正式发表
  13. [轻松学会shell编程]-3、grep、正则表达式、awk的详细用法、分析系统自带的两个文件(functions和network)
  14. Unity 编辑器下运行没有声音
  15. android TVBOX OTT IPTV
  16. 小米手环3 NFC 自定义 门禁卡数据
  17. 2020年非上海生源应届普通高校毕业生落户材料办理流程及注意事项
  18. python3.5中import cv2报错
  19. 计算机科学与技术与光电,光电信息科学与工程考研科目有哪些?
  20. 新产品开发的项目管理

热门文章

  1. vijos 1476 旅游规划题解
  2. Unicode转义(\uXXXX)的编码和解码
  3. asp.net中web.config配置节点大全详解
  4. php安装完成以后要复制php.ini文件
  5. C#调用windows api的要点
  6. Request.ServerVariables获取环境变量
  7. ASP.NET 2.0 HttpHandler实现生成图片验证码(示例代码下载)
  8. ASP.NET 学习历程
  9. 调用系统中的默认EMAIL程序
  10. 使用man在线手册页