element的上传组件el-upload源码分析

821 阅读1分钟

前言

大家好,我叫竹业,今天来深入了解一下el-upload组件的用法。

组件使用

首先来看下组件的使用

  • autoUpload为false时,可以通过submit方法来触发
<template>
  <el-upload
    class="upload-demo"
    action="https://jsonplaceholder.typicode.com/posts/"
    :on-preview="handlePreview"
    :on-remove="handleRemove"
    :before-remove="beforeRemove"
    multiple
    :limit="3"
    :on-exceed="handleExceed"
    :file-list="fileList"
  >
    <el-button size="small" type="primary">点击上传</el-button>
    <div slot="tip" class="el-upload__tip">
      只能上传jpg/png文件,且不超过500kb
    </div>
  </el-upload>
</template>
<script>
export default {
  data() {
    return {
      fileList: [
        {
          name: "food.jpeg",
          url: "https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100",
        },
        {
          name: "food2.jpeg",
          url: "https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100",
        },
      ],
    };
  },
  methods: {
    handleRemove(file, fileList) {
      console.log(file, fileList);
    },
    handlePreview(file) {
      console.log(file);
    },
    handleExceed(files, fileList) {
      this.$message.warning(
        `当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
          files.length + fileList.length
        } 个文件`
      );
    },
    beforeRemove(file, fileList) {
      return this.$confirm(`确定移除 ${file.name}?`);
    },
  },
};
</script>

源码实现

upload组件

实现上传的组件是upload组件

  • 1.点击整个upload组件,触发handleClick方法,js触发input的点击
  • 2.input的handleChange方法,拿到文件后,将数据初始化,调用post方法进行文件上传
  • 3.上传文件之前,会执行beforeUpload钩子函数,返回false,则停止请求
  • 4.post方法里的this.httpRequest(options)可以自己定义,默认有一个上传的方法,单独写在ajax.js
<template>
  <div tabindex="0" :class="['el-upload', `el-upload--${listType}`]" @click="handleClick">
    <upload-dragger v-if="drag" @file="uploadFiles" :disabled="disabled"></upload-dragger>
    <slot v-else></slot>
    <input class="el-upload__input" type="file" ref="input" :name="name" @change="handleChange" :multiple="multiple" :accept="accept" />
  </div>
</template>
<script>
export default {
    methods: {
        handleClick() {
          if (!this.disabled) {
            this.$refs.input.value = null;
            this.$refs.input.click();
          }
        },
        handleChange(ev) {
          const files = ev.target.files;

          if (!files) return;
          this.uploadFiles(files);
        },
        uploadFiles(files) {
          // ...
          // 对文件进行数据格式化,并自动上传
          postFiles.forEach(rawFile => {
            // 文件数据进行处理
            this.handleStart(rawFile);
            if (this.autoUpload) this.upload(rawFile);
          });
        },
        upload(rawFile) {
          this.$refs.input.value = null;
          // 没有beforeUpload钩子函数,直接执行上传文件接口
          if (!this.beforeUpload) {
            return this.post(rawFile);
          }
            
         const before = this.beforeUpload(rawFile);
         if (before !== false) {
            this.post(rawFile);
          } else {
            this.onRemove(null, rawFile);
          }
        },
        
        // 上传文件
        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];
            }
          };
          // httpRequest方法默认内部实现的上传方法,也可以自定义方法
          const req = this.httpRequest(options);
          this.reqs[uid] = req;
          // 返回的req也可以支持promise
          if (req && req.then) {
            req.then(options.onSuccess, options.onError);
          }
        },
        // 中断请求
        abort(file) {
          const { reqs } = this;
          if (file) {
            let uid = file;
            if (file.uid) uid = file.uid;
            if (reqs[uid]) {
              reqs[uid].abort();
            }
          } else {
            // 不传参数,中断所有xhr请求
            Object.keys(reqs).forEach((uid) => {
              if (reqs[uid]) reqs[uid].abort();
              delete reqs[uid];
            });
          }
        },
        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
            };
            this.uploadFiles.push(file);
            this.onChange(file, this.uploadFiles); // 触发传入的onChange函数
        }
      }
}
</script>

httpRequest默认方法

  • 1.首先new XMLHttpRequest
  • 2.注册上传的进度回调函数onprogress
  • 3.传入的data参数,添加到formData里,传递给后台
  • 4.onerror、onload事件触发成功或失败的回调
  • 4.传入的headers参数,添加到请求头里
  • 5.传入了withCredentials参数为true,设置withCredentials为true
  • 6.xhr.send(formData)发送请求
function upload(option) {
  const xhr = new XMLHttpRequest();
  // 请求的url
  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();
  // formData的参数
  if (option.data) {
    Object.keys(option.data).forEach(key => {
      formData.append(key, option.data[key]);
    });
  }
  // 默认将文件流添加到formData
  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);
    
  // 不同源是够携带cookie
  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;
}

最后

这里主要分析了upload.vue组件和ajax.js的httpRequest方法,这个组件还是实现很简单的,最后祝大家周末愉快~