2

最近使用了elementUI提供的upload组件上传文件,总结下使用过程中的一些心得。本文不会介绍如何使用elementUI中的upload组件,因为官网本身就已经介绍的很详细了。

本文主要包括以下几个问题:

  1. 文件校验是如何做的,能否同时校验多种文件格式;
  2. 上传文件的时候能否自定义拖拽样式;
  3. 点击的时候如何实现手动上传。

序言

  1. 本文涉及到源码的部分,以v2.15.1为例;
  2. upload组件源代码在/packages/upload文件夹下面;

文件校验是如何做的,能否同时校验多种文件格式

upload组件提供了两种上传文件的方式:

  1. input元素方式:通过input元素触发系统的文件选择窗口来选择文件;
  2. 拖拽方式:通过原生drag和drop API来选择文件。

针对上述两种方式,upload组件提供了属性accept限制校验文件格式。这里,我使用了两个词:

  1. 限制:从源头层面上限制,也就是不是特定的格式就选择不了,适用于input元素方式
  2. 校验:从结果层面上校验,也就是在最后一步才能发现选择不了,同时适用于上述两种方式,其中校验又包括两种:

    1. 源码本身包含的校验逻辑;
    2. 自定义校验。

接下来我们结合upload组件源码来分析下实现原理。

限制

限制形式的主要原理就是原生input元素的accept属性(源码/packages/upload/src/upload.vue文件第206行):

<input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>

当没有添加accept属性的时候,系统的文件选择框中的文件都是可选的:

<input type="file" />

image.png

当添加了accept之后,比如.png,系统的文件选择框中只有符合格式的文件才是可选的:

<input type="file" accept=".png" />

image.png

如何支持多种文件格式上传呢?看下原生input元素accept属性的文档,会发现accept是支持逗号分割的多种文件格式的:

<input type="file" accept=".png,.jpg,.jpeg" />

image.png
开发的时候发现一个有意思的问题,就是如果accept里面.jpg和.jpeg只写一个,另外一种格式也是可以选择的:

<input type="file" accept=".png,.jpeg" />
// 或者
<input type="file" accept=".png,.jpg" />

image.png
所以,jpg和jpeg有啥关系?查找了下资料,发现这两种格式并没有任何区别,至于为什么会有两个名字,说是先有了jpeg,然后Windows的早期版本规定文件的扩展名只能是三个字符,所以就变成了jpg。感兴趣的可以参考这篇文章

所以,

  1. 只通过accept的形式限制文件类型有时候可能不准确;
  2. Windows系统可能会有兼容性问题,也就是即使提供了accept属性,其他格式的文件也是可选的;

这个时候就只能依赖下面的校验方式了。

源码本身包含的校验逻辑

当使用拖拽方式上传文件的时候,是可以拖拽任何文件的,所以只能在拖拽完成的时候去校验文件格式。那么,应该去校验哪些格式呢?从upload组件使用者的角度来说,无论是input元素方式还是拖拽方式,都应该是保持统一的。也就是,原生input的accept属性支持的格式,在使用拖拽方式的时候,也都得支持。

所以,先看下input的accept属性支持的格式类型:

  1. 文件扩展名形式,以.开头;
  2. MIME类型;
  3. "audio/*" "video/*" "image/*"。

upload组件源码针对上述三种类型做的相应处理(/packages/upload/src/upload-dragger.vue文件onDrop方法)如下:

this.$emit('file', [].slice.call(e.dataTransfer.files).filter(file => {
  const { type, name } = file;
  const extension = name.indexOf('.') > -1
    ? `.${ name.split('.').pop() }`
    : '';
  const baseType = type.replace(/\/.*$/, '');
  return accept.split(',')
    .map(type => type.trim())
    .filter(type => type)
    .some(acceptedType => {
      if (/\..+$/.test(acceptedType)) { // 1. 扩展名形式
        return extension === acceptedType;
      }
      if (/\/\*$/.test(acceptedType)) { // 3. "audio/*" "video/*" "image/*"
        return baseType === acceptedType.replace(/\/\*$/, '');
      }
      if (/^[^\/]+\/[^\/]+$/.test(acceptedType)) { // 2. MIME类型
        return type === acceptedType;
      }
      return false;
    });
}));

自定义校验

upload组件提供了自定义校验的钩子函数:before-upload。根据该函数的返回值决定是继续还是终止(/packages/upload/src/upload.vue文件第91行):

const before = this.beforeUpload(rawFile);

上传文件的时候能否自定义拖拽样式

结论:不能

尝试

dragEvent有一个dataTransfer属性,这个属性有一个setDragImage方法,该方法看似可以用来自定义拖拽样式。但是,这个方法有一个使用限制:只能在dragstart事件中调用。

但是当从操作系统拖拽文件到浏览器中的时候,dragstart事件是不会触发的,所以setDragImage方法不能实现我们的目的。

点击的时候如何实现手动上传

upload组件dom中包含一个不显示的input元素,当点击组件的时候触发input元素的click事件(/packages/upload/src/upload.vue):

handleClick() {
  if (!this.disabled) {
    this.$refs.input.value = null;
    this.$refs.input.click();
  }
}

总结

如有错误,欢迎留言讨论。

参考资料

  1. file drag and drop API
  2. upload组件GitHub地址
  3. jpg和jpeg文件的区别

luckness
6.2k 声望5.1k 粉丝