『设计』ES6原生实战Uploader工具类(从设计到实现)( 二 )

初始化 - _init 这里初始化做了几件事:维护一个内部文件数组uploadFiles , 构建input标签 , 绑定input标签的事件 , 挂载dom 。
为什么需要用一个数组去维护文件 , 因为从需求上看 , 我们的每个文件需要一个状态去追踪 , 所以我们选择内部维护一个数组 , 而不是直接将文件对象交给上层逻辑 。
由于逻辑比较混杂 , 分多了一个函数_initInputElement进行初始化input的属性 。
class Uploader {// ..._init () {this.uploadFiles = [];this.input = this._initInputElement(this.setting);// input的onchange事件处理函数this.changeHandler = e => {// ...};this.input.addEventListener('change', this.changeHandler);this.setting.wrapper.appendChild(this.input);}_initInputElement (setting) {const el = document.createElement('input');Object.entries({type: 'file',accept: setting.accept,multiple: setting.multiple,hidden: true}).forEach(([key, value]) => {el[key] = value;})''return el;}} 看完上面的实现 , 有两点需要说明一下:

  1. 为了考虑到destroy()的实现 , 我们需要在this属性上暂存input标签与绑定的事件 。 后续方便直接取来 , 解绑事件与去除dom 。
  2. 其实把input事件函数changeHandler单独抽离出去也可以 , 更方便维护 。 但是会有this指向问题 , 因为handler里我们希望将this指向本身实例 , 若抽离出去就需要使用bind绑定一下当前上下文 。
上文中的changeHanler , 来单独分析实现 , 这里我们要读取文件 , 响应实例choose事件 , 将文件列表作为参数传递给loadFiles 。
为了更加贴合业务需求 , 可以通过事件返回结果来判断是中断 , 还是进入下一流程 。
this.changeHandler = e => {const files = e.target.files;const ret = this._callHook('choose', files);if (ret !== false) {this.loadFiles(ret || e.target.files);}}; 通过这样的实现 , 如果显式返回false , 我们则不响应下一流程 , 否则拿返回结果||文件列表 。 这样我们就将判断格式不符 , 超出大小限制等等这样的逻辑交给上层实现 , 响应样式控制 。 如以下例子:
uploader.on('choose', files => {const overSize = [].some.call(files, item => item.size > 1024 * 1024 * 10)if (overSize) {setTips('有文件超出大小限制')return false;}return files;});状态事件绑定与响应 简单实现上文提到的_callHook , 将事件挂载在实例属性上 。 因为要涉及到单个choose事件结果控制 。 没有按照标准的发布/订阅模式的事件中心来做 , 有兴趣的同学可以看看tiny-emitter的实现 。
class Uploader {// ...on (evt, cb) {if (evt && typeof cb === 'function') {this['on' + evt] = cb;}return this;}_callHook (evt, ...args) {if (evt && this['on' + evt]) {return this['on' + evt].apply(this, args);}return;}}装载文件列表 - loadFiles 传进来文件列表参数 , 判断个数响应事件 , 其次就是要封装出内部列表的数据格式 , 方便追踪状态和对应对象 , 这里我们要用一个外部变量生成id , 再根据autoUpload参数选择是否自动上传 。
let uid = 1class Uploader {// ...loadFiles (files) {if (!files) return false;if (this.limit !== -1 &&files.length &&files.length + this.uploadFiles.length > this.limit) {this._callHook('exceed', files);return false;}// 构建约定的数据格式this.uploadFiles = this.uploadFiles.concat([].map.call(files, file => {return {uid: uid++,rawFile: file,fileName: file.name,size: file.size,status: 'ready'}}))this._callHook('change', this.uploadFiles);this.setting.autoUpload && this.upload()return true}}