下述源码分析基于 Element v2.15.9 版本

前提

在解析源码之前,先阐述其重点使用的两个基础内容:

<input type="file">

使用 type=“file” 的 元素使得用户可以选择一个或多个元素以提交表单的方式上传到服务器上,或者通过 Javascript 的 File API 对文件进行操作。

其支持附加属性:

属性 说明
accept 一个或多个 唯一文件类型说明符 描述允许的文件类型
capture 捕获图像或视频数据的源
files FileList 列出了已选择的文件
multiple 布尔值,如果出现,则表示用户可以选择多个文件

XMLHttpRequest

XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。

其支持的关键属性/方法/事件:

属性/方法/事件 说明
upload 代可以通过对其绑定事件来追踪它的进度
setRequestHeader() 设置 HTTP 请求头的值。必须在 open() 之后、send() 之前调用
open() 初始化一个请求
abort() 如果请求已被发出,则立刻中止请求
send() 发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回
load 请求成功完成时触发
error 当 request 遭遇错误时触发

el-upload 多数 prop 是借助上述两个原生形式实现的。

el-upload 执行逻辑

  1. 定义 trigger slot 或使用默认 slot

    packages/upload/src/index.vue render()

    render(h) {let uploadList;if (this.showFileList) {uploadList = ( <UploadList ...>);}const uploadData = {props: {/* 注入的props传递给<upload> */},ref: 'upload-inner'};const trigger = this.$slots.trigger || this.$slots.default;// 内部组件 <upload> 包裹const uploadComponent = <upload {...uploadData}>{trigger}</upload>;return (<div>{ this.listType === 'picture-card' ? uploadList : ''}{this.$slots.trigger? [uploadComponent, this.$slots.default]: uploadComponent}{this.$slots.tip}{ this.listType !== 'picture-card' ? uploadList : ''}</div>);
    }
    
  2. 内部组件 <upload> 绑定事件

    packages/upload/src/upload.vue render()

    render(h) {let { handleClick, ... } = this;const data = {class: {'el-upload': true},on: {click: handleClick,keydown: handleKeydown}};data.class[`el-upload--${listType}`] = true;return (// 外层绑定了 click/keydown 事件<div {...data} tabindex="0" >{drag? <upload-dragger disabled={disabled} on-file={uploadFiles}>{this.$slots.default}</upload-dragger>: this.$slots.default}// <input type="file"> 选择本机文件<input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input></div>);
    }// 打开选择文件弹窗
    handleClick() {if (!this.disabled) {this.$refs.input.value = null;this.$refs.input.click();}
    }
    
  3. 通过 <input type="file"> on-change 事件获取上传文件

  4. 判断文件是否超出 limit prop 限制,超出后调用 on-exceed

    这里需要注意,区分自动上传、手动上传

    handleChange(ev) {const files = ev.target.files;if (!files) return;this.uploadFiles(files);
    },uploadFiles(files) {if (this.limit && this.fileList.length + files.length > this.limit) {this.onExceed && this.onExceed(files, this.fileList);return;}let postFiles = Array.prototype.slice.call(files);if (!this.multiple) { postFiles = postFiles.slice(0, 1); }if (postFiles.length === 0) { return; }postFiles.forEach(rawFile => {// 手动、自动上传前都会触发this.onStart(rawFile);if (this.autoUpload) this.upload(rawFile);});
    }
    
  5. onStart(rawFile),这里会调用 on-chagne

    handleStart(rawFile) {rawFile.uid = Date.now() + this.tempIndex++;let file = {status: 'ready',...};if (this.listType === 'picture-card' || this.listType === 'picture') {try {file.url = URL.createObjectURL(rawFile);} catch (err) { ... }}this.uploadFiles.push(file);// 调用 on-changethis.onChange(file, this.uploadFiles);
    }
    

    所以,on-change 的执行顺序早于 before-upload,且不区分是否自动

  6. 【手动上传】this.refs['upload'].submit

    手动上传,官方给出的方式是调用 el-upload 组件的 submit()

    submit() {this.uploadFiles.filter(file => file.status === 'ready').forEach(file => {this.$refs['upload-inner'].upload(file.raw);});
    }
    

    只有 ready 的才可以调用 upload

  7. this.upload(rawFile)

    upload(rawFile) {this.$refs.input.value = null;if (!this.beforeUpload) {return this.post(rawFile);}// before-upload 在该阶段执行!const before = this.beforeUpload(rawFile);if (before && before.then) {before.then(processedFile => {// 忽略了逻辑分支判断this.post(rawFile); }, () => {// ①this.onRemove(null, rawFile);});} else if (before !== false) {this.post(rawFile);} else {// ①this.onRemove(null, rawFile);}
    }
    

    before-upload 返回 false/Promise.reject() 会调用 on-remove

  8. this.post(rawFile) Ajax 提交文件

    post(rawFile) {options = { headers, withCredentials, action, filename, data, file }const req = this.httpRequest(options)this.reqs[uid] = req;if (req && req.then) {req.then(options.onSuccess, options.onError);}
    }
    

    通过 XMLHttpRequest 封装,会调用 on-progresson-successon-error

常见问题

  1. 可以作为form表单元素使用

    <el-form><el-form-item><el-upload></el-upload></el-form-item>
    </el-form>
    

    disabled 的状态,可以沿用 el-form 的 disabled 状态

    computed: {uploadDisabled() {// 这段代码存在逻辑漏洞,当 form 表单为 disabled,el-upload 为 fasle 不起作用return this.disabled || (this.elForm || {}).disabled;}
    }
    

    注意:form 表单元素普遍存在上述问题:
    this.$options.propsData.hasOwnProperty('disabled') ? this.disabled : (this.elForm || {}).disabled;

    但,其不会触发 el.form.change 事件,即不会触发验证流程

  2. 如何设置了 file-list prop,内部会监听其变化

    <el-upload :file-list="fileList"></el-upload>
    

    内部实现:

    watch: {fileList: {immediate: true,handler(fileList) {this.uploadFiles = fileList.map(item => {item.uid = item.uid || (Date.now() + this.tempIndex++);item.status = item.status || 'success';return item;});}}
    }
    

    这意味,一旦指定 file-list 后,自己业务中操作全部可以围绕此对象 fileList 展开即可,不要同其提供的 filelist 混淆使用。

    // on-change 事件
    handlerChange (file, filelist) {this.fileList.push(file) /* 或者其他操作,无需通过 filelist 处理(组件内部对象引用)*/
    }
    
  3. 非自动上传 before-upload 失效

    通过上述源码分析可知【第7步】,其是在 this.upload(rawFile) 确认提交环节才执行,对于非自动上传,调用 submit() 时才触发,并非不触发。

    这意味,在非自动上传场景下,验证文件基础信息(大小、类型、个数等),需要在 on-change 中处理!

  4. 非自动上传后端校验失败后,该文件不能再上传(对于携带formdata字段唯一性校验,很常见)

    通过上述源码分析可知【第6步】,非自动上传调用 submit() 方法,只针对 file 为 ready 状态文件调用上传方法;而一旦上传过,该文件状态会改变为 success

    handleProgress(ev, rawFile) {file.status = 'uploading';
    }
    handleSuccess(res, rawFile) {file.status = 'success';
    }
    handleError(err, rawFile) {file.status = 'fail';
    }
    

    此时,处理方案有两种:① 修改 file 状态为 ready;② 自定义上传 ajax 方法(不调用submit)!

  5. 限制只有一个文件,如果存在已上传文件,希望覆盖操作

    通过上述源码分析可知【第4步】,el-upload 提供了 limit 属性,如果将其设置为 1,会在选择文件时进行判断,如果超出不会做任何操作,此时达不到覆盖的效果。

    这意味,我们不能通过 limit 控制(不设置 limit),在 on-change 中修改 filelist!

    handleChange (file, fileList) {// 只保留一个文件if (fileList.length > 1) {// 这里直接改了引用值 组件内部 uploadFilesfileList.splice(0, 1)}
    }
    

    如果定了 file-list prop <el-upload :file-list="fileList"></el-upload>,则直接通过控制自己定义的 filelist 即可(常见问题2中我们有提及,内部会watch该 filelist)

总结

el-upload 提供了诸多处理,为我们日常开发提供了便利性,同时也存在着一些边界没有处理。所以,这里建议如下:

【关于校验】放到 on-change 中实现,而不是 before-upload

  1. 这样无需关心是否为自动上传执行问题(非自动掉用submit,才触发before-upload
  2. before-upload 返回 false,会执行 on-remove,整体比较混乱

【关于是否自定义 file-list】

  1. 如果存在存量file,一定要使用file-list,便于初始化展示
  2. 对于文件列表有其他业务要求可自定义,否则不建议使用,避免引用之间的传递问题

【非自动上传】auto-upload=false

  1. 如果存在其他【上传时附带的额外参数】后端校验问题,建议自定义上传 ajax(而非修改 file status = ready)

关于el-upload看这一篇就够了相关推荐

  1. Vue使用Swiper看这一篇就够了

    Vue使用Swiper看这一篇就够了 此案例实现需求 完成swiper动态异步数据下的slide渲染 自定义分页器样式 解决loop:true设置时的事件丢失问题 swiper鼠标移入/移出 暂停/开 ...

  2. api网关选型_如何轻松打造百亿流量API网关?看这一篇就够了(下)

    如何轻松打造百亿流量API网关?看这一篇就够了(上) 上篇整体描述了网关的背景,涉及职能.分类.定位环节,本篇进入本文的重点,将会具体谈下百亿级流量API网关的演进过程. 准备好瓜子花生小板凳开始积累 ...

  3. python装饰器功能是冒泡排序怎么做_传说中Python最难理解的点|看这完篇就够了(装饰器)...

    https://mp.weixin.qq.com/s/B6pEZLrayqzJfMtLqiAfpQ 1.什么是装饰器 网上有人是这么评价装饰器的,我觉得写的很有趣,比喻的很形象 每个人都有的内裤主要是 ...

  4. serviceloader java_【java编程】ServiceLoader使用看这一篇就够了

    转载:https://www.jianshu.com/p/7601ba434ff4 想必大家多多少少听过spi,具体的解释我就不多说了.但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问 ...

  5. docker 删除所有镜像_关于 Docker 镜像的操作,看完这篇就够啦 !(下)| 文末福利...

    紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...

  6. mysql ip比较大小_MySQL优化/面试,看这一篇就够了

    原文链接:http://www.zhenganwen.top/articles/2018/12/25/1565048860202.html 作者:Anwen~ 链接:https://www.nowco ...

  7. 基础 | 零散的MySql基础记不住,看这一篇就够啦

    ❝ 这是小小本周的第二篇,本篇将会着重的讲解关于MySql基础的内容,MySql基础看这一篇就够啦. ❞ 送书反馈与继续送书 之情小微信公众号第一次送书,Java深度调试技术,书已经被中奖者麦洛签收, ...

  8. 【系统架构设计师】软考高级职称,一次通过,倾尽所有,看完这篇就够了,论软件架构设计的重要性、本篇论文“未通过考试”,供分析参考

    [系统架构设计师]软考高级职称,一次通过,倾尽所有,看完这篇就够了,学习方法和技巧这里全都有. 论软件架构设计的重要性.本篇论文未通过考试(不合格),供分析参考. 目录 摘要 正文 结尾 摘要 201 ...

  9. 深度好文:云网络丢包故障定位,看这一篇就够了~

    深度好文:云网络丢包故障定位,看这一篇就够了~ https://mp.weixin.qq.com/s/-Q1AkxUr9xzGKwUMV-FQhQ Alex 高效运维 今天 来源:本文经授权转自公众号 ...

  10. 代理后台中间件_Golang Gin 实战(十三)| 中间件详解看这一篇就够了

    6000字大章带你死磕Golang Gin中间件 在Gin的整个实现中,中间件可谓是Gin的精髓.一个个中间件组成一条中间件链,对HTTP Request请求进行拦截处理,实现了代码的解耦和分离,并且 ...

最新文章

  1. 适定、超定和欠定方程的概念
  2. 世界上第一位程序员是位美女——AdaLovelace【有图为证】
  3. makefile的命令包定义及使用
  4. R语言实战应用精讲50篇(三十一)-R语言入门系列-tidyverse数据分析流程
  5. 奖金16万!首届电子商务AI算法大赛ECAA报名开启
  6. python rabitmq_python RabbitMQ队列使用
  7. oracle的rank,over partition 使用 和lead
  8. C语言程序设计二期末考试,C语言程序设计期末考试试卷2.doc
  9. 浙江高校计算机等级考试二级办公,浙江省高校计算机等级考试二级(高级办公)Word操作提示.doc...
  10. 可到了关键部分的作文
  11. 二.路径规划---二维路径规划实车实现---gmapping+amcl+map_server+move_base
  12. include引用php,php使用include 和require引入文件的区别
  13. Python2.7升级至Python3.6
  14. 一键调整PCB丝印,超级好用
  15. 贝叶斯定理,从白袜到飞机失事再到人工智能
  16. 教育部高等教育司指定大学生必读100本书目
  17. 动态规划之硬币面值组合问题
  18. A. Frog Jumping
  19. Hi,你想要的在线创建架构图都在这儿!(二)
  20. 心脏和字节只有一个跳动,生命和工资只能拼一个多多

热门文章

  1. 瑞昱RTL8201G(I)-VB-CG 规格应用--电口传输距离(500M)之王
  2. 微软新品发布会汇总:更新Surface硬件产品 首发智能耳机
  3. 英语语法中的状语介绍
  4. Android系统字体
  5. python最大递归层次_练习题-Python的最大递归层数
  6. javascript中的instance和typeof
  7. Phonics 自然拼读法 c/k,e,h,r,m,d Teacher:Lamb
  8. 优麒麟 19.04 即将发布,华为、阿里云、重大、360四大境像站鼎力支持!
  9. 《计算机网络》笔记-第1章计算机网络和因特网
  10. 计算机在电力的应用,浅析计算机技术在电力系统中的应用