知道事件捕获! 🤪 但不会用来实现批量拖拽上传?(二)

简介: 知道事件捕获! 🤪 但不会用来实现批量拖拽上传?

批量拖拽上传

上面的方式能不能支持 批量拖拽上传 呢,直接来试试看:

image.png

上述我们选择了 3 个文件,也触发了 3beforeUpload,但在其中的 this.inputFiles 的长度却一直是 0,而 this.uploadedFiles 的长度在变化,导致最终的判断条件出现了问题。

从源码查找原因

源码位置:element-ui\packages\upload\src\upload.vue

  • props.dragtrue 时,会渲染 <upload-dragger> 组件
  • props.dragfalse 时,会渲染外部指定的 默认插槽的内容

image.png

再去看看 <upload-dragger> 组件 的具体内容,大致如下:

image.png

显然,当用户通过拖拽的方式实现上传时,是通过 HTML5 中的拖放事件来实现的,那么选择的文件自然不能通过 input.files 的方式获取到,这也就是文章开头提到的问题。

事件捕获 — 事件捕获都知道,那你倒是用起来啊!

通过查看源码之后发现拖拽时一定会触发 onDrop,那么既然不能通过 input.files 的方式获取用户选中文件的总数量,那么我们就在父级的 onDrop 事件中再去获取用户选择的文件内容(可通过 event.dataTransfer.files 获取),即利用事件捕获的方式

DataTransfer 对象用于保存拖动并放下(drag and drop)过程中的数据,它可以保存一项或多项数据,这些数据项可以是 一种 或 多种 数据类型

<template>
  <div class="easy-upload" @drop.capture="onDrop">
    <el-upload :ref="refName" :name="aliasName" v-bind="$attrs" :auto-upload="false" :before-upload="beforeUpload" :http-request="httpRequest" :on-change="onChange">
      <slot></slot>
    </el-upload>
  </div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component } from 'vue-property-decorator';
import { post } from '@utils/request';
import ElementUI from 'element-ui';
let _uploadId_ = 0;
@Component({})
export default class EasyUpload extends Vue {
  uploadedFiles: File[] = [];
  inputFiles: File[] = [];
  refName = '_upload_ref_';
  aliasName = '_upload_name_';
  created() {
    this.initConfig();
  }
  // 初始化组件数据
  initConfig() {
    if (this.$attrs.name) this.aliasName = this.$attrs.name;
    this.refName += _uploadId_;
    this.aliasName += _uploadId_;
    _uploadId_++;
  }
  formatParams() {
    const formData = new FormData();
    // 文件相关参数
    this.uploadedFiles.forEach((file) => {
      formData.append(this.$attrs.name || 'file', file);
    });
    // 额外参数
    const { data } = this.$attrs;
    if (data) {
      Object.keys(data).forEach((key) => {
        formData.append(key, data[key]);
      });
    }
    return formData;
  }
  async httpRequest(options: any) {
    const formData = this.formatParams();
    const res = await post(this.$attrs.action, formData, true);
    // 重置操作
    this.resetUpload();
  }
  beforeUpload() {
    // 是否需要调用上传接口
    return this.uploadedFiles.length === this.inputFiles.length;
  }
  onChange(file, fileList) {
    if (file.status === 'ready') {
      this.uploadedFiles.push(file.raw);
    }
    // 由于开启了事件捕获,因此 ondrop 只要被触发,this.inputFiles 就会有值
    // 如果 this.inputFiles 没有值,证明当前是点击上传的方式
    if (this.inputFiles.length === 0) {
      this.inputFiles = Array.from((<HTMLInputElement>document.getElementsByName(this.aliasName)[0]).files || []);
    }
    (this.$refs[this.refName] as ElementUI.Upload).submit();
  }
  onDrop(event) {
    // 事件捕获提前执行,为 inputFiles 赋值
    this.inputFiles = Array.from(event.dataTransfer.files);
  }
  resetUpload() {
    this.uploadedFiles = [];
    this.inputFiles = [];
    (this.$refs[this.refName] as ElementUI.Upload).clearFiles();
  }
}
</script>
复制代码

效果演示

批量点击上传批量拖拽上传 效果如下:

事情的后续

同事 A 的另一种解决方案

同事 A 看完这篇文章不仅没有 点赞 + 收藏,反而说他实现找到了一种更合适的方式,邀我一同欣赏他的操作,大致思路非常简单:

  • 由于 Upload 组件的 onChange 事件会被多次执行(即用户选择多少个文件,就会执行多少次) ,并且 onChange(file, fileList) 的参数 fileList 只有最后一次执行时才会拿到用户选择文件的总数
  • 因此 同事 A 就在 onChange 事件中使用了 $nextTick 包裹整个 onChange 的内容,大致如下:
onChange(file, fileList) {
    this.$nextTick(() => {
      file.status == 'ready' && this.uploadFiles.push(file.raw);
      let files: any = (<HTMLInputElement>document.getElementsByName(this.name)[0]).files;
      this.fileTotal = this.drag ? fileList.length : files.length;
      if (this.uploadFiles.length === this.fileTotal) {
        (this.$refs[this.refName] as any).submit();
      }
    });
}
复制代码

@【同事 A】 等我分析完,总该给我【点赞 + 收藏】了吧!!!

合理分析

为什么可用?

显然,使用了 $nextTick 之后 onChange(file, fileList) 的参数 fileList 就一定是用户选择的文件总数,因为 $nextTick 包裹的内容是一个 微/宏任务,这意味着这段逻辑不会立马执行,而等到它执行时,由于 fileList 参数是对应源码中的 this.uploadFiles,即等到 $nextTick 的回调函数被执行时,对应的 this.uploadFiles 已经是包含了用户选择的所有文件,因此 this.uploadFiles.length === this.fileTotal 这个判断是可以的。

源码位置:element-ui\packages\upload\src\index.vue

handleStart(rawFile) {
  rawFile.uid = Date.now() + this.tempIndex++;
  let file = {
    status: 'ready',
    name: rawFile.name,
    size: rawFile.size,
    percentage: 0,
    uid: rawFile.uid,
    raw: rawFile
  };
  if (this.listType === 'picture-card' || this.listType === 'picture') {
    try {
      file.url = URL.createObjectURL(rawFile);
    } catch (err) {
      console.error('[Element Error][Upload]', err);
      return;
    }
  }
  this.uploadFiles.push(file);
  this.onChange(file, this.uploadFiles);
},
复制代码

能用就真的合适用吗?

虽然说上述方式确实能够实现对应的需求,但却并不一定合适:

  • 由于 onChange 事件会被多次执行,导致 $nextTick 被多次执行,意味着 微/宏任务队列 中会出现多个没有必要被执行的任务
  • 比如用户选择文件总数为 4onChange 执行 4 次,$nextTick 执行 4 次,微/宏任务队列 中会被添加 4 个任务,而这些任务都已经能够访问最终的 fileList 总数,没有必要被多次推入任务队列中
  • 相比来说,只需要执行一次即可 ``,比如:
hasChange = false;
  onChange(file, fileList) {
    // hasChange 的加持下,$nextTick 只会执行一次
    !this.hasChange &&
      this.$nextTick(() => {
        // 可以拿到用户选择的全部文件列表
        this.uploadFiles = fileList;
        (this.$refs[this.refName] as any).submit();
      });
    this.hasChange = true;
  }
复制代码

扩展思路

有了上面的思路,仍然可以进行扩展,只要在 onChange 的最后一次执行时,保存 fileList 和进行 submit 提交,就可以实现最终的目的,比如用【防抖】来实现 onChange,这样多次触发 onChange 时,也只有最后一次会执行,并且最后一次已经可以拿到我们需要的所有数据。

最后

统一回复私信

想交个朋友的可以添加 微信号:Mr10212021 ,也欢迎关注同名公众号《熊的猫》,文章会同步更新!

上述功能需求还是比较简单的,从源码角度来理解也并不困难,经过上述的剖析相信你对自己实现所谓的 点击上传拖拽上传 应该也有自己的理解,完全可以自己去实现一个,并提供给它们对应的 批量上传 方式。

希望本文对你所有帮助!!!

目录
相关文章
|
1月前
|
JavaScript 数据安全/隐私保护
2024了,你会使用原生js批量获取表单数据吗
2024了,你会使用原生js批量获取表单数据吗
47 4
|
6月前
|
搜索推荐
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
|
6月前
|
前端开发
前端实现拖拽上传
前端实现拖拽上传
101 1
|
6月前
|
JavaScript
点击导出所选数据(原生js)
点击导出所选数据(原生js)
40 0
|
6月前
【sgUploadTray】自定义组件:上传托盘自定义组件,可实时查看上传列表进度。
【sgUploadTray】自定义组件:上传托盘自定义组件,可实时查看上传列表进度。
|
6月前
|
存储 前端开发 JavaScript
点击按钮时触发防抖
点击按钮时触发防抖
80 0
|
前端开发
添加按钮的两种方式
添加按钮的两种方式
87 0
Echarts链接操作弹出窗口防止重复触发点击事件的解决方案
Echarts链接操作弹出窗口防止重复触发点击事件的解决方案
129 0
|
前端开发 JavaScript
知道事件捕获! 🤪 但不会用来实现批量拖拽上传?(一)
知道事件捕获! 🤪 但不会用来实现批量拖拽上传?
119 0
知道事件捕获! 🤪 但不会用来实现批量拖拽上传?(一)
移动端touch拖动事件和click事件冲突问题解决
移动端touch拖动事件和click事件冲突问题解决
238 0