最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Element-UI是怎么实现上传组件的

    正文概述 掘金(一介散修)   2020-12-29   585

    相信很多同学都使用过element的上传组件,那它是怎么实现的呢?我们知道,涉及到文件上传的话都要使用到FileList对象,而FileList通常又来自于input[type='file']控件。我们从这个控件入手,一步一步展开。

    我使用的element是"2.13.2"的,我们找到/packages/upload/src/upload.vue,我们可以看到,组件使用的是render函数,在函数中有这样一行代码:

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

    render中使用的是JSX,可以看出这实际就是渲染了一个input[type='file']控件,并且它绑定了关键的事件处理函数handleChange。我们跳到handleChange看一下它的源码:

     handleChange(ev) {
        const files = ev.target.files;
        if (!files) return;
        this.uploadFiles(files);
      }
    

    handleChange只做了一件事情,在拿到FileList对象的时候,将FileList传到uploadFiles函数进行一些前置条件到处理。

    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);
      });
    },
    

    我们可以看到函数之中对文件对上传数量做了判断,然后又对每个File对象单独处理了一遍。看postFiles.forEach内的onStart调用,这是父组件传递过来的函数,我们找到/packages/upload/src/index.vue,并在322行找到组件的渲染部分:

    const uploadComponent = <upload {...uploadData}>{trigger}</upload>;
    

    我们看到upload组件接收了一个参数uploadData,往上翻可以看到对应的值:

    const uploadData = {
      props: {
        type: this.type,
        drag: this.drag,
        action: this.action,
        multiple: this.multiple,
        'before-upload': this.beforeUpload,
        'with-credentials': this.withCredentials,
        headers: this.headers,
        name: this.name,
        data: this.data,
        accept: this.accept,
        fileList: this.uploadFiles,
        autoUpload: this.autoUpload,
        listType: this.listType,
        disabled: this.uploadDisabled,
        limit: this.limit,
        'on-exceed': this.onExceed,
        'on-start': this.handleStart,
        'on-progress': this.handleProgress,
        'on-success': this.handleSuccess,
        'on-error': this.handleError,
        'on-preview': this.onPreview,
        'on-remove': this.handleRemove,
        'http-request': this.httpRequest
      },
      ref: 'upload-inner'
    };
    

    可以看到onStart的值就是this.handleStartthis.handleStart处理了三件事:

    • 使用当前时间加计数器变量生成uid
    • 将File对象加工成组件自定义对象
    • 将处理过的FileList传递给组件上层的钩子onChange(文件状态改变时的钩子)
    // 使用当前时间加计数器变量生成uid
    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);
    

    我们看到上面的代码在使用了URL.createObjectURLURL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL对象表示指定的 File 对象或 Blob 对象。什么意思呢,你可以简单的认为这是用来将File对像转换成预览图的。需要注意的是,URL.createObjectURL在使用完后并不会主动销毁,我们需要在组件销毁的时候调用 URL.revokeObjectURL() 方法来释放。可以看到组件在beforeDestroy时做了这步操作:

    beforeDestroy() {
      this.uploadFiles.forEach(file => {
        if (file.url && file.url.indexOf('blob:') === 0) {
          URL.revokeObjectURL(file.url);
        }
      });
    }
    

    onStart方法最主要的目的是为了将每次上传后的FileList与当前上传的File传递给上层使用方使用,通过调用上层传递的onChange事件,将File、FileList传递给上层调用方。

    到了这里我们可以看出element的上传组件其实更多的是在处理File、FileList来给到上层调用方使用。有了File对象,我们自己其实就已经可以将文件上传到服务器了,不过element帮我们将这部分也做好了。我们回到upload.vue,在84行:

    upload(rawFile) {
      this.$refs.input.value = null;
    
      if (!this.beforeUpload) {
        return this.post(rawFile);
      }
    
      const before = this.beforeUpload(rawFile);
      if (before && before.then) {
        before.then(processedFile => {
          const fileType = Object.prototype.toString.call(processedFile);
    
          if (fileType === '[object File]' || fileType === '[object Blob]') {
            if (fileType === '[object Blob]') {
              processedFile = new File([processedFile], rawFile.name, {
                type: rawFile.type
              });
            }
            for (const p in rawFile) {
              if (rawFile.hasOwnProperty(p)) {
                processedFile[p] = rawFile[p];
              }
            }
            this.post(processedFile);
          } else {
            this.post(rawFile);
          }
        }, () => {
          this.onRemove(null, rawFile);
        });
      } else if (before !== false) {
        this.post(rawFile);
      } else {
        this.onRemove(null, rawFile);
      }
    }
    

    如果对照官方使用文档,我们可以看到许多钩子函数到身影beforeUpload、onRemove,这些我们不细讲。我们主要看看element是如何帮我们将File对象处理并上传到服务器的。将所有的条件判断忽略,我们可以看到最终的上传使用的是this.post方法。post代码如下:

    post(rawFile) {
      const { uid } = rawFile;
      const options = {
        headers: this.headers,
        withCredentials: this.withCredentials,
        file: rawFile,
        data: this.data,
        filename: this.name,
        action: this.action,
        onProgress: e => {
          this.onProgress(e, rawFile);
        },
        onSuccess: res => {
          this.onSuccess(res, rawFile);
          delete this.reqs[uid];
        },
        onError: err => {
          this.onError(err, rawFile);
          delete this.reqs[uid];
        }
      };
      const req = this.httpRequest(options);
      this.reqs[uid] = req;
      if (req && req.then) {
        req.then(options.onSuccess, options.onError);
      }
    }
    

    post处理了一些使用到的参数,并将这些参数传递给this.httpRequest,这就是我们最后要讲的上传步骤。找到/packages/upload/src/ajax.js

    // 错误消息提示处理
    function getError(action, option, xhr){}
    //返回内容处理
    function getBody(xhr) {}
    //上传
    function upload(option) {}
    

    ajax.js一共有三个函数,upload处理了主要的上传逻辑:

    function upload(option) {
      if (typeof XMLHttpRequest === 'undefined') {
        return;
      }
    
      const xhr = new XMLHttpRequest();
      const action = option.action;
    
      if (xhr.upload) {
        xhr.upload.onprogress = function progress(e) {
          if (e.total > 0) {
            e.percent = e.loaded / e.total * 100;
          }
          option.onProgress(e);
        };
      }
    
      const formData = new FormData();
    
      if (option.data) {
        Object.keys(option.data).forEach(key => {
          formData.append(key, option.data[key]);
        });
      }
    
      formData.append(option.filename, option.file, option.file.name);
    
      xhr.onerror = function error(e) {
        option.onError(e);
      };
    
      xhr.onload = function onload() {
        if (xhr.status < 200 || xhr.status >= 300) {
          return option.onError(getError(action, option, xhr));
        }
    
        option.onSuccess(getBody(xhr));
      };
    
      xhr.open('post', action, true);
    
      if (option.withCredentials && 'withCredentials' in xhr) {
        xhr.withCredentials = true;
      }
    
      const headers = option.headers || {};
    
      for (let item in headers) {
        if (headers.hasOwnProperty(item) && headers[item] !== null) {
          xhr.setRequestHeader(item, headers[item]);
        }
      }
      xhr.send(formData);
      return xhr;
    }
    
    

    可以看到函数中主要使用了XMLHttpRequest、FormData

    XMLHttpRequest

    • XMLHttpRequest.withCredentials 属性是一个Boolean类型,它指示了是否该使用类似cookies,authorization headers(头部授权)或者TLS客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。在同一个站点下使用withCredentials属性是无效的。
    • XMLHttpRequest.upload 属性返回一个 XMLHttpRequestUpload对象,用来表示上传的进度。这个对象是不透明的,但是作为一个XMLHttpRequestEventTarget,可以通过对其绑定事件来追踪它的进度。
    • XMLHttpRequestEventTarget.onerror 是XMLHttpRequest 事务由于错误而失败时调用的函数。
    • XMLHttpRequestEventTarget.onload 是 XMLHttpRequest 请求成功完成时调用的函数。
    • 只读属性 XMLHttpRequest.status 返回了XMLHttpRequest 响应中的数字状态码。status码是标准的HTTP status codes。
    • XMLHttpRequest.setRequestHeader() 是设置HTTP请求头部的方法。此方法必须在 open() 方法和 send() 之间调用。如果多次对同一个请求头赋值,只会生成一个合并了多个值的请求头。

    FormData

    • FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式
    • FormData.set 和 append() 的区别在于,如果指定的键已经存在, FormData.set 会使用新值覆盖已有的值,而 append() 会把新值添加到已有值集合的后面。

    element通过XMLHttpRequest发起请求,通过FormData组装File最后达到上传目的。


    起源地下载网 » Element-UI是怎么实现上传组件的

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元