iView-Upload组件分析

2,343 阅读4分钟

源码分析

  • xhr相关知识点
  • Ajax要点分析
  • 拖拽事件以及粘贴事件
  • 具体实现
  • 总结

xhr相关知识点

  • XMLHttpRequest.upload 属性返回一个 XMLHttpRequestUpload对象,用来表示上传的进度。 通过onprogress属性进行监听,是在 XMLHttpRequest 完成之前周期性调用的函数。
xhr.upload.onprogress = function progress(e) {
    event.loaded 已传输的数据量
    event.total 总共的数据量
    if (e.total > 0) {
        e.percent = e.loaded / e.total * 100;
    }
};
  • XMLHttpRequestEventTarget.onload 是 XMLHttpRequest 请求成功完成时调用的函数。使用该属性使得判断更加简单
使用onreadystatechange属性
xhr.onreadystatechange = function () {
  if(xhr.readyState === XMLHttpRequest.DONE) {//当前状态码为'DONE'(4),表示下载操作已完成
    console.log(xhr.responseText)
  }
}
使用onload属性
xhr.onload = function onload() {
    ...
}; 
  • HTTP 响应状态代码指示特定 HTTP 请求是否已成功完成。
  1. 信息响应(100-199)
  2. 成功响应(200-299)
  3. 重定向(300-399)
  4. 客户端错误(400-499)
  5. 服务器错误(500-599)

Ajax的封装

源码链接 github.com/ElemeFE/ele… 该源码不考虑IE低版本的情况,即ActiveXObject不在考虑范围。

  • 创建一个FormData对象,将相关的数据进行添加,包括文件
const formData = new FormData();

if (option.data) {
    Object.keys(option.data).map(key => {
        formData.append(key, option.data[key]); //通过遍历添加新的属性值
    });
}

formData.append(option.filename, option.file); //添加文件
  • 状态码status小于200或大于等于300的状态进行错误提示
xhr.onload = function onload() {
    if (xhr.status < 200 || xhr.status >= 300) {
        return option.onError(getError(action, option, xhr), getBody(xhr));
    }
};  
  • 设置请求头信息,忽略headers的继承属性同时值不能为null;这里进行headers.hasOwnProperty(item)的判断,可以避免这种情况:请求 中的headers继承共用headers中的请求头信息。
const headers = option.headers || {};

for (let item in headers) {
    if (headers.hasOwnProperty(item) && headers[item] !== null) {
        xhr.setRequestHeader(item, headers[item]);
    }
}

拖拽事件以及粘贴事件

  • 拖拽事件中,对需要拖动的元素赋予draggable属性;在释放位置所在的元素上使用drop事件,同时在拖动的元素上dragover阻止默认行为以启用drop
document.addEventListener("dragover", function( event ) {
    // 阻止默认动作以启用drop
    event.preventDefault();
}, false);
  • 利用DataTransfer这个对象可以在拖拽过程中传递数据;涉及Upload组件的属性: e.DataTransfer.files在拖动操作中表示文件列表,是一个类数组
dragevent.dataTransfer.setData("text", xxx); //拖动时设置数据
dropevent.dataTransfer.getData("text"); //释放时获取数据
  • 粘贴事件paste: 事件处理程序可以通过调用事件的 clipboardData 属性上的 getData()访问剪贴板内容;涉及Upload组件的属性: e.clipboardData.files获取到粘贴的文件列表,同样也是一个类数组
event.clipboardData.getData()

具体实现

Upload组件提供了3种选择文件的方式: 点击,拖拽和粘贴;点击的方式通过调用原生的input点击事件,监听input上的change事件获取到文件列表;拖拽和粘贴的方式则是通过事件drop,paste直接获取到文件列表;由于在点击方式中使用的是点击input实现的,所以在单选时弹出文件选择框时已经限制只能选中一个,但是拖拽和粘贴在单选时仍然可以选择多个文件,所以需要进行过滤,保证单选模式下只能存在一个文件。校验文件格式及大小,符合条件就开始发起请求,为每个文件添加属性(uid,percentage,status...),最后,通过回调函数获取上传进度,成功请求,失败请求相关参数。

  • 事件的处理,通过动态触发input元素的click事件,值得注意的是选择完成后,需要手动设置value值为空,保证下次选择同一文件还会触发input的change事件
//点击方式
handleClick () {
    ...
    this.$refs.input.click();
},
handleChange (e) {
    const files = e.target.files;
	...
    this.$refs.input.value = null;
},
//拖拽方式
onDrop (e) {
    this.uploadFiles(e.dataTransfer.files);
},
//粘贴方式
handlePaste (e) {
    this.uploadFiles(e.clipboardData.files);
}
  • 对文件列表初始化
uploadFiles (files) {
    let postFiles = Array.prototype.slice.call(files); // 类数组转数组
    if (!this.multiple) postFiles = postFiles.slice(0, 1); //单选下保证所有的文件列表只有一条数据

    if (postFiles.length === 0) return;

    postFiles.forEach(file => {
        this.upload(file); 
    });
},
  • 创建每个文件对应的数据集合,并且发起请求,通过值传递为每个数据集合添加uid
post (file) {
	this.handleStart(file);
    ajax({
        ...,
        onProgress: e => {
            this.handleProgress(e, file);
        },
        onSuccess: res => {
            this.handleSuccess(res, file);
        },
        onError: (err, response) => {
            this.handleError(err, response, file);
        }
    });
}
  • 请求回调函数,为对应的处理函数返回值
handleProgress (e, file) {
    const _file = this.getFile(file); //获取到文件的详细信息
    this.onProgress(e, _file, this.fileList); //事件,当前文件,文件列表
    _file.percentage = e.percent || 0; //进度
}
handleSuccess (res, file) {
      const _file = this.getFile(file);

      if (_file) {
          _file.status = 'finished'; //更新状态
          _file.response = res;

          this.onSuccess(res, _file, this.fileList);
      }
  },
  handleError (err, response, file) {
      const _file = this.getFile(file);
      const fileList = this.fileList;

      _file.status = 'fail'; //更新状态

      fileList.splice(fileList.indexOf(_file), 1); 

      this.onError(err, response, file);
  },

总结

Upload组件涉及的API较广,需要了解;还有一些概念,比如代码的值传递,基于引用类型的复制,灵活地为每个文件集合添加uid属性,同时又能快捷方便删除项。