关于el-upload看这一篇就够了(代码片段)

奋飛 奋飛     2022-12-03     379

关键词:

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

前提

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

<input type="file">

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

其支持附加属性:

属性说明
accept一个或多个 唯一文件类型说明符 描述允许的文件类型
capture捕获图像或视频数据的源
filesFileList 列出了已选择的文件
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-change
      this.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) 
        // 这里直接改了引用值 组件内部 uploadFiles
        fileList.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看这一篇就够了(代码片段)

下述源码分析基于Elementv2.15.9版本前提在解析源码之前,先阐述其重点使用的两个基础内容:<inputtype="file">使用type=“file”的元素使得用户可以选择一个或多个元素以提交表单的方式上传到服务器上,... 查看详情

关于el-upload看这一篇就够了(代码片段)

下述源码分析基于Elementv2.15.9版本前提在解析源码之前,先阐述其重点使用的两个基础内容:<inputtype="file">使用type=“file”的元素使得用户可以选择一个或多个元素以提交表单的方式上传到服务器上,... 查看详情

elasticsearch入门,看这一篇就够了(代码片段)

Elasticsearch入门,看这一篇就够了前言可视化工具kibanakibana的安装kibana配置kibana的启动Elasticsearch入门操作操作index创建index索引别名有什么用删除索引查询索引exist索引操作document插入document查询document删除document更新document使用... 查看详情

[转]关于深度学习,看这一篇就够了

关于深度学习,看这一篇就够了原文地址:http://www.dlworld.cn/XueXiSuanFa/13.html[日期:2016-04-26]来源:36氪 作者:[字体:大 中 小]    编者按:本文作者王川,投资人,中科大少年班校友,现居加州硅谷,个人微信号... 查看详情

handler看这一篇就够了(代码片段)

Handler使用首先来熟悉一下Handler的四种使用方式,如果比较熟悉可以直接跳过:通过sendMessage消息机制来发送sendEmptyMessage(int);//发送一个空的消息sendMessage(Message);//发送消息,消息中可以携带参数sendMessageAtTime(Message,long... 查看详情

最全排序算法及优化,看这一篇就够了(代码片段)

最全排序算法总结看这一篇就够了没有经过总结的知识是沉重的,无用的瞧一瞧~博健的LeetCode题解:Gitbook版本传送门博健的LeetCode题解:CSDN传送门有趣的CSS:Gitbook传送门前端进阶笔记:Gitbook传送门目录... 查看详情

关于反爬虫,看这一篇就够了

编者:本文来自携程酒店研发部研发经理崔广宇在第三期【携程技术微分享】上的分享,以下为整理的内容概要。墙裂建议点击视频回放,“现场”围观段子手攻城狮大崔,如何高智商&高情商地完美碾压爬虫。。。关... 查看详情

关于反爬虫,看这一篇就够了(转)

https://segmentfault.com/a/1190000005840672者:本文来自携程酒店研发部研发经理崔广宇在第三期【携程技术微分享】上的分享,以下为整理的内容概要。墙裂建议点击视频回放,“现场”围观段子手攻城狮大崔,如何高智商&高情商地... 查看详情

css渐变背景看这一篇就够了(代码片段)

CSS渐变背景看这一篇就够了在我们自己设计网页的时候,为了好看美观,颜色可谓是最让人头疼的一部分。尤其是在配色上又找不到一些好看的网站。今天我就来记录一些好看的渐变式背景,和一些常用的颜色网站。... 查看详情

☀️javanio?看这一篇就够了!!☀️(代码片段)

文章目录一、NIO简介1.1NIO概述1.2NIO&IO分析1.2.1IO操作流程1.2.2面向流与面向缓冲区1.2.3阻塞与非阻塞1.2.4.同步与异步二、Buffer基本应用2.1Buffer概述2.2Buffer基本应用三、Channel基本应用3.1Channel概述3.2FileChannel基本应用3.3SocketChanel基... 查看详情

☀️javanio?看这一篇就够了!!☀️(代码片段)

文章目录一、NIO简介1.1NIO概述1.2NIO&IO分析1.2.1IO操作流程1.2.2面向流与面向缓冲区1.2.3阻塞与非阻塞1.2.4.同步与异步二、Buffer基本应用2.1Buffer概述2.2Buffer基本应用三、Channel基本应用3.1Channel概述3.2FileChannel基本应用3.3SocketChanel基... 查看详情

关于类的加载机制和反射机制只看这一篇就够了,分析的非常详细(代码片段)

类加载机制的原理1.启动JVM2.将需要运行的class文件加载到虚拟机内存中3.找到主类,开始执行主函数加载步骤:1.先委托父类加载类,如果父类已经加载,就不需要再次加载,如果父类没有加载,再由本加载器加载2.解析类路径,... 查看详情

想要弄懂groupby看这一篇就够了(代码片段)

一、前言groupby关键字,不管是工作中还是面试都会经常被用到,所以弄懂它是非常有必要的。要弄懂groupby那我们就得联合着:聚合函数、groupby、having一块讲解。讲之前我们先准备一张表:二、聚合函数为了讲好groupby我们必须... 查看详情

逆转单向链表看这一篇就够了java(代码片段)

逆转单向链表逆转前:1->2->3->4->5->null逆转后:5->4->3->2->1->null个人博客地址:逆转单向链表方法一、循环迭代publicNodereverse(Nodehead)if(head==null||head.next==null)returnhead;//取前面节点Nodepre=head;//取后面 查看详情

关于反爬虫,看这一篇就够了

编者:本文来自携程酒店研发部研发经理崔广宇在第三期【携程技术微分享】上的分享,以下为整理的内容概要。墙裂建议点击视频回放,“现场”围观段子手攻城狮大崔,如何高智商&高情商地完美碾压爬虫... 查看详情

系统性的学会pandas看这一篇就够了(代码片段)

作者:MaSizhou https://blog.csdn.net/weixin_45901519/article/details/1129808221、Pandas数据结构2008年WesMcKinney开发出的库专门用于数据挖掘的开源python库以Numpy为基础,借力Numpy模块在计算方面性能高的优势基于matplotlib,能够简便的... 查看详情

jquery框架超详细dom操作看这一篇就够了!(代码片段)

目录写在前面一、内容操作1.html()2.text()3.val()二、属性操作(1)通用属性操作1.attr():2.removeAttr()3.prop()4.removeProp()5.attr和prop区别(2)对class属性操作1.addClass()2.removeClass()3.toggleClass()4.css()三、 查看详情

redux从入门到进阶,看这一篇就够了!(代码片段)

Redux,带你从入门到进阶🌂序言☂️一、基础知识1、Redux概念简述2、Redux的工作流程🎃二、使用Antd实现TodoList页面布局1、在项目中使用Antd2、使用Antd实现TodoList的基本布局3、创建redux中的store(1)创建storeÿ... 查看详情