当前位置: 首页 > 编程日记 > 正文

我是如何设计 Upload 上传组件的

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...

相关文章:

设计模式之资料摘录

本文主要是摘录了网上的一些资料&#xff0c;目前在慢慢学习这些东西&#xff0c;还谈不上个人的理解&#xff0c;离在项目中的应用也很遥远&#xff0c;后期会补上个人理解与实际项目中的应用 总体来说设计模式分为三大类&#xff1a; 创建型模式&#xff0c;共五种&#xff1…

Web.Config文件配置之连接默认错误页

在一些网站中&#xff0c;当网络地址发生错误时&#xff0c;通常会自动跳转到一个页面&#xff0c;并在该页面显示错误信息&#xff0c;此功能叶可以通过配置Web.Config文件配置实现。例如访问者在访问网站时出现错误&#xff0c;程序将跳转到默认页面error.aspx。在Web.Config…

新版rust怎么拆除建筑_市政工程造价怎么学 龙岗园林市政造价课程

课程对象&#xff1a;1、建筑相关专业或者非建筑专业应届毕业生&#xff1b;2、从事施工、资料、安全、招标等岗位&#xff0c;欲转岗做造价工作的人员&#xff1b;3、企业新入职工程造价人员&#xff1b;4、其他专业/行业&#xff0c;欲转行做造价的人员。5、已经在造价行业从…

HDFS Federation与HDFS High Availability详解

HDFS Federation    NameNode在内存中保存文件系统中每个文件和每个数据块的引用关系&#xff0c;这意味着对于一个拥有大量文件的超大集群来说&#xff0c;内存将成为限制系统横向扩展的瓶颈。在2.0发行版本系列中引入的Federation HDFS允许  系统通过添加NameNode实现扩…

win32的一个售票程序,收获有非常的多

先秀一下我的收获吧&#xff01; 1、在创建非模态对话框的时&#xff0c;需要用到createdialog函数&#xff0c;第二个参数需要注意是填写对话框的资源标识符&#xff08;id&#xff09;&#xff0c;之后需要调用showwindow来显示对话框&#xff1b;或者不这么做也行&#xff0…

从理论到实践 全面理解HTTP/2

前言 为了降低加载时间&#xff0c;相信大多数人都做过如下尝试 Keep-alive: TCP持久连接&#xff0c;增加了TCP连接的复用性&#xff0c;但只有当上一个请求/响应完全完成后&#xff0c;client才能发送下一个请求Pipelining: 可同时发送多个请求&#xff0c;但是服务器必须严格…

开机不进去桌面执行gui_电脑系统崩溃进不去,简单几步设置U盘,快速找回桌面重要文件...

hello~我可爱的粉丝们&#xff01;今天粮小白分享一个电脑系统崩溃无法开机&#xff0c;怎么把电脑数据保存备份的小技能&#xff0c;就来就点个关注吧。每天更新生活必备技能哦。电脑系统崩溃进不去&#xff0c;简单几步设置U盘&#xff0c;快速找回桌面重要文件近些年来&…

java遍历给定目录,树形结构输出所有文件,包括子目录中的文件

http://www.cnblogs.com/jenson138/p/4691418.html import java.io.File;public class ReadDirectory {// 文件所在的层数private int fileLevel;/*** 生成输出格式* param name 输出的文件名或目录名* param level 输出的文件名或者目录名所在的层次* return 输出的字符串*/pu…

数据千万条,备份第一条,数据找不回,老板两行泪

2019独角兽企业重金招聘Python工程师标准>>> 开工第一天&#xff0c;请带着你的回忆看下文&#xff0c;想想你这些年删过的库&#xff0c;被删过的库。。。 数据库备份是个老生常谈的话题&#xff0c;看似很简单&#xff0c;但在实际操作过程中&#xff0c;运维人员…

【转】Visual Studio 2010 架构图之用例图(UML Use Case Diagram)

这篇文章是来自园子的&#xff0c;我只是把自己觉得重要的地方&#xff0c;做了更鲜明的标记和解释。 使用Visual Studio 2010可以在项目中添加架构图&#xff0c;项目可以是ASP.NET、Windows Forms、...&#xff0c;也可以创建单独的Modeling Projects。 可以通过下图方式在项…

spi通讯不需要地线吗_抖音真的需要养号吗?不需要!千万别被割了韭菜

“抖音新账号一定要先养号&#xff0c;再发布作品。”这句话&#xff0c;你是不是听很多人跟你讲过&#xff1f;包括我也曾在之前的推文里建议大家先养号&#xff0c;并且说的有理有据。&#xff08;要打脸了&#xff09;但是&#xff0c;这是一句谣传&#xff01;暂且不说谣言…

Linux学习之setjmp和longjmp函数

nsetjmp和longjmp函数实现函数之间的跳转(需包含头文件"setjmp.h")&#xff1a;函数原型&#xff1a;int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val); setjmp函数用于设置跳转的目的位置&#xff0c;longjmp函数进行跳转。env&#xff1a;保留了需要返…

Ubuntu 和 Redhat / Fedora 服务管理命令对比表(附Fedora16新的服务管理工具systemctl )...

以 apache/httpd 服务作为例子 任务 Red Hat / Fedora Ubuntu Ubuntu (with sysv-rc-conf or sysvconfig) 立即启动/停止某服务 service httpd start invoke-rc.d apache start service apache start 启动时自动加载 chkconfig httpd on update-rc.d apache defaults…

酷派、华为不能打印log解决办法

我发现我的手机无法再eclipse里面输出自己打的log.i等log&#xff0c;一开始还以为是我手机root或刷过机的问题。 最终非常偶然找到了解决的方法&#xff1a; 酷派大神F1&#xff0c;F2在出厂时将log的级别做了限制。 解除限制的方法是&#xff1a;拨号盘输入*20121220# ->…

80070583类不存在_Java自学-接口与继承 内部类

&#xfeff;Java 内部类内部类分为四种&#xff1a; 非静态内部类 静态内部类 匿名类 本地类步骤 1 : 非静态内部类非静态内部类 BattleScore “战斗成绩” 非静态内部类可以直接在一个类里面定义比如&#xff1a; 战斗成绩只有在一个英雄对象存在的时候才有意义 所以实例化Ba…

java 初识对象和对象引用的关系

在接触java之前就知道了java的对象引用&#xff0c;但概念一直很模糊&#xff0c;看了《Head First in Java》后觉得书中的例子很有趣&#xff0c;便分享给大家。 首先要搞清对象引用是什么&#xff1f; 我们都知道变量可以存放数据&#xff0c;就像杯子一样&#xff0c;不同大…

IOS UI 代码创建UIButton,UITextField,UILabel

//add a Label UILabel *label[[UILabelalloc]initWithFrame:CGRectMake(100,20, 150, 40)]; [label setText:"i am a label "]; label.font[UIFontsystemFontOfSize:20]; label.textAlignmentNSTextAlignmentLeft; //文字内容自适应标签宽度 label.adjustsFontSizeT…

Hash join 和nested loop

Hash join 和nested loop 总所周知&#xff0c;Oracle数据库常用的两种优化器&#xff1a;RBO&#xff08;rule-based-optimizer&#xff09;和CBO(cost-based-optimizer)。目前更多地采用CBO(cost-based-optimizer)基于开销的优化器。在CBO方式下&#xff0c;Oracle会根据表及…

minus oracle 顺序_oracle minus的用法来一波

Oracle Minus关键字SQL中有一个MINUS关键字&#xff0c;它运用在两个SQL语句上&#xff0c;它先找出第一条SQL语句所产生的结果&#xff0c;然后看这些结果有没有在第二个SQL语句的结果中。如果有的话&#xff0c;那这一笔记录就被去除&#xff0c;而不会在最后的结果中出现。如…

我现在的vimrc配置文件

我现在的vimrc配置文件 runtime! debian.vim "设置编码 set encodingutf-8 set fencsutf-8,ucs-bom,shift-jis,gb18030,gbk,gb2312,cp936 set fileencodingsutf-8,ucs-bom,chinese"语言设置 set langmenuzh_CN.UTF-8" command WQ wq command Wq wq command W w…

UVA 10269 Adventure of Super Mario

UVA_10269 由于马里奥的飞行距离有限&#xff0c;因此为了方便处理&#xff0c;我们首先用floyd预处理出马里奥可以飞行的两点间的最短路&#xff0c;然后再将图分成K1层用SPFA求最短路即可。 #include<stdio.h>#include<string.h>#define MAXD 130#define MAXN 20…

“5G杀手级应用”Cloud VR 华为如何打响5G第一枪

雷锋网消息&#xff0c;近日华为在上海召开华为云 5G Cloud VR服务发布会暨5G Cloud VR开发者沙龙&#xff0c;Cloud VR有何潜力成为5G第一批杀手级应用&#xff0c;华为又在其中扮演怎样的角色。Cloud VR和5G更配生产决定消费&#xff0c;消费反作用于生产&#xff0c;对于5G也…

昆仑通态通用版找不到驱动_2021深圳新安西门子伺服驱动电机回收合作共赢

2021深圳新安西门子伺服驱动电机回收合作共赢 一个企业,应尽量做到PLC的机型统主要考虑到以下三方面问题&#xff1a;机型统其模块可互为备用,便于备品备件的采购和管理。机型统其功能和使用方法类似,有利于技术力量的培训和技术水平的提高。机型统其外部设备通用,资源可共享,易…

熟人Dubbo 系列1-Dubbo什么

Dubbo阿里巴巴内部SOA治理方案和服务的核心框架。每天2000 个服务提供3,000,000,000 次訪问量支持&#xff0c;并被广泛应用于阿里巴巴集团的各成员网站。Dubbo自2011年开源后&#xff0c;已被很多非阿里系公司使用。Dubbo[]是一个分布式服务框架&#xff0c;致力于提供高性能和…

CentOS源码安装GitLab汉化版第3版

软件版本&#xff1a; 软件版本CentOS7.5GraphicsMagick1.3.31Git2.21.0Ruby2.5.3Go1.12Node.js10.15.2PostgreSQL11.2Redis5.0.3GitLab11.8.0 汉化版Nginx1.14.21. 安装依赖 yum -y install libicu-devel patch gcc-c readline-devel zlib-devel libffi-devel openssl-devel m…

用JSP+JDBC开发Web程序

以前一直想找个纯粹的JSPJDBC开发Web程序的架构&#xff0c;一直没有找到合适的&#xff0c;后来自己写了一个简单实现&#xff0c;并实施了几个项目。 此开发架构的特点是&#xff1a; 1.架构技术简单&#xff0c;只包含JSP和JDBC&#xff0c;不需要学习即可快速开发Web应用&a…

catia怎么创建约束快捷键_答疑 | CATIA结构树无法显示怎么办?

问题有小伙伴反馈&#xff0c;设计过程中&#xff0c;CATIA的结构树不见了……怎么办&#xff1f;问题听起来很简单&#xff0c;但总能难倒一些新手。原因与解决方案&#xff1a;下面针对产生该问题的不同的原因&#xff0c;提出不同的解决方案。第一种情况原因&#xff1a;结构…

【UVA】11992 - Fast Matrix Operations(段树模板)

主体段树&#xff0c;要注意&#xff0c;因为有set和add操作&#xff0c;当慵懒的标志下推。递归优先set&#xff0c;后复发add&#xff0c;每次运行set行动add马克清0 WA了好几次是由于计算那一段的时候出问题了&#xff0c;可笑的是我对着模板找了一个多小时的错。 #include&…

记录一次MySQL两千万数据的大表优化解决过程,提供三种解决方案

问题概述使用阿里云rds for MySQL数据库&#xff08;就是MySQL5.6版本&#xff09;&#xff0c;有个用户上网记录表6个月的数据量近2000万&#xff0c;保留最近一年的数据量达到4000万&#xff0c;查询速度极慢&#xff0c;日常卡死。严重影响业务。 问题前提&#xff1a;老系统…

SQL Server 2008 下载地址(微软官方网站)

哪里有sqlserver2008下载&#xff1f;2011-9-24 23:58提问者&#xff1a;ooseestars | 浏览次数&#xff1a;3252次2011-9-26 11:38最佳答案SQL Server 2008 下载地址(微软官方网站) 中文版(3.28GB)&#xff1a;http://sqlserver.dlservice.microsoft.com/dl/download/B/8/0/B8…