前言
上文是前段时间写了一篇根据公司的业务需求我是如何封装组件,也算是对那段忙碌的工作一份小小的总结吧。有时在忙碌的开发过程之后,我们需要停下脚步去思考自己写的代码是否具有可读性?是否还可以再健壮一些?同时也要去总结业务,回过头来看看自己在业务的需求的基础上去设计的组件代码是否是合理的?是否还可以更好一些?
我想这样的四个问号会一直伴随着我,因为我知道那是成长的必经之路。
业务小需求
做过后台管理系统应该都知道文件上传这样一个功能,说到文件上传就想到自己曾研究的大文件上传的案例,并总结了这样的一篇文章。(ps:实现多个大文件拖拽上传+大文件分片上传+断点续传+文件预览)。
收到的业务需求并不是如上的直接将文件传送给后端的这样的上传功能,而是要将文件的内容进行解析成后端想要的数据格式,再将解析之后的数据送给后端,这样来实现文件的上传。
组件封装
我是通过element-ui
的上传组件和js-xlsx
库来实现这样的一个需求。
安装插件
npm install element-ui xlsx -S
如何设计组件
实现这个功能并不难,思路就是先收集文件再将文件先解析成JSON
数据,最后发送后端。但如何更好的设计组件,让组件在整个项目复用最大化,值得去思考。
这里我是将这个组件拆分成模板组件和功能组件。模板组件组是用来展示UI以及收集文件,功能组件是用来将文件内容解析成数据。因为一个项目下来文件上传的功能可能不仅仅是将文件解析成数据,也可能是直接将整个文件进行上传,那要是将文件内容解析成JSON
数据这样的功能都放在一个组件里,这样组件就会显得很臃肿。而且这样也可以实现对模板组件的复用,以及最大化的复用这个功能组件。
那如何将模板组件和功能组件连接在一起呢?这里的核心是利用Vue的Mixins
可以混入另一个组件的功能。具体可查看:Mixins
自定义组件
我是用了el-upload
的组件作为模板来自定义组件。开发这个功能需要自定义上传的方法,所以我通过on-change
事件来收集文件和on-remove
属性来移除文件。秉承着高内聚低耦合的思想,我将这两个属性直接在el-upload
组件上绑定,而不是通过在页面使用自定义组件时传入属性再跨阶级传入到el-upload
。因为最终的目的都是为了拿到文件数据去请求接口,那何不在那么自己将数据进行处理好呢?所以,为了方便我在内部整理好数据之后,通过外部传入一个callback函数来执行拿到文件数据之后的后续操作,callback函数这是一个必传的配置属性,它表示将文件数据整合后的后续操作。(ps:比如调用接口等等...)
因为要将文件读取成JSON
数据,所以自定义组件配置了一个属性headerStr
,这个属性是对象型数组,里面承载着要传给后端的字段key
和要从表格里哪列读取内容str
,以及该字段所对应的数据类型type
。那么如何使用这个配置项,留个疑问,后续讲述。如下图:
自定义组件定义了一个multiple
属性,值为true
或false
,用来开启是否是多文件上传;组件也自配置了start
和end
属性,用来表示表格的从哪一列到哪一列结束;组件内部也进行了文件格式的一一校验。组件内部也对callback函数进行了一层包装,将它包装成一个Promise对象,这样做的目的是为了使多文件可以并发上传。
自定义组件可将文件收集成fileList之后,那么如何对文件进行数据解析,这时需要一个功能组件。
将文件解析成JSON数据
在上面讲述了使用Vue的Mixins
,所以我在自定义组件混入了功能组件UploadXlsx
,那自定义组件就可以直接访问功能组件的方法。
在功能组件里,暴露了两个方法:
- 一个方法是
handlerReaderFile
用来读取文件内容,在这里通过FileReader
来读取一份文件,并且使用xlsx
将表格对象转成JSON
对象(ps:这里是使用xlsx
库提供的XLSX.utils.sheet_to_json
方法。如何使用js-xlsx
这个库,详细可看github的js-xlsx)。该方法返回了一个Promise
对象。在编码的过程中一开始只考虑了一份.xlsx表格文件只有一个sheet的情况下,但实际情况一份xlsx表格可能有多个sheet的情况(ps:也就是一份文件多份sheet的情况下),所以要进行统一的处理(ps:如果是多份sheet,则对每一份sheet进行解析)。这里是通过数组队列保存每一份sheet解析完成的JSON
值之后再resolve
给自定义模板组件。在这个函数内部,通过自定义组件传入的start
和end
属性进行文件表头的校验,不符合要求的不给与上传。具体的逻辑可查看源码。
来看看通过xlsx
库生成的JSON
数据是长什么样的:
- 那另一个方法是
handlerUploadResult
用来生成真正后端想要的JSON
数据。这里就使用到headerStr这个配置项核心,通过str
属性和表格生成的JSON数据的key进行比对匹配,并且通过type
属性进行数据类型的改装,就可生成真正的JSON
数据。大致的思路:
基本上功能组件已经完成了。在回头看看自定义模板组件,在将收集到的文件LIST做处理时通过使用功能组件暴露的handlerReaderFile
方法来实现将文件解析成JSON
数据,并且调用callback函数。因为handlerReaderFile
方法返回的是数组队列,所以我在自定义组件内部使用Promise.all
进行文件的并发上传。我将这个返回值传到刚刚上面所讲述的包装callback的那个函数,并将参数进行循环,然后通过使用Promsie包装再resolve调用callback函数。最后通过调用包装函数所返回的值作为Promise.all
的参数。具体的实现逻辑可查看源码。
使用组件
-
如何使用:
-
组件的展示:
编码思考
-
因一开始只思考了处理一个sheet的情况,而遗漏了多个sheet的情况。其实只考虑了一个sheet的情况,功能还是可以使用的,但是这个功能组件的代码并不健壮。如果下次我的同事一份表格编写了3份sheet呢?那么组件就遗漏数据处理了。所以多去思考各种可能性多观察不同的业务可能性,把自己的代码变得更健壮是值得实践的。
-
通过将组件拆分成功能组件和模板组件,我觉得是有必要的。因为文件上传实现的方案可有多种可能性。现在是将表格数据解析再上传,那下次直接将文件上传呢?那么该模板就可以被复用,而且对于所定义的函数,属性并没有什么冲突,反而可让这个组件更好维护。而且做过后台管理系统的都大概都知道比如弹框这种的功能无处不在,如何在项目当中更好的设计这类的需求,我觉得就可以把弹框组件(UI)和是否显示弹框(功能组件)抽离出来,这样整个项目都可复用这个功能组件,遇到类似的弹框还可以做个配置项去设计。
-
给组件定制配置项,比如
headerStr
和callback
。那么下次不一样的表头相同的上传方式,那么这个组件就可以直接被复用了。而且组件尽可能的高内聚低耦合
,能在内部进行处理的就一定放在内部处理,将其结果暴露给外部的使用者。
源码地址
源地址可查看:源码
总结
开发完这个需求之后,我对Promise
和Object.assign
这些基础的东西也有更深入的认识。我还是觉得动手去封装组件是有必要的,而且随着时间的成长,尽可能的要有一套自己的组件库。
最后,对各大掘友有帮助的话希望赏个 star ~(ps:不要吝啬哦)