前言
前段时间写了一个列表框架公共组件,可以快速灵活搭建各种列表页面,且具有高扩展性。
开发背景:有一个内容管理系统(项目采用vue框架,UI框架使用view-design),内容种类不断的在扩展,最开始的文章,后来的游戏,接下来的教程……
每一种内容都有:待处理列表,已提交列表,已发布列表,回收站列表,草稿箱。每一种列表的特点都不太相同,却基本是一个模式:
1、筛选排序条件各不相同,有的列表没有筛选排序条件,改变条件立即同步刷新列表(使用时,可以灵活定制各自的筛选排序)
2、有些列表需要记住并回显上次的选择的条件,甚至记住并标记出上次查看的列表项
3、有的列表需要对列表项进行多选,在此基础上进行批量操作(如批量删除,批量上下架),或者批量上传配音等,有的列表什么都不需要(可以扩展其他批量操作)
4、有的列表需要周期性的更新数据,有的不需要
由于数据的差异,已有列表不能复用。于是便开发出一个列表框架 ListFrame:
使用它,只需要提供接口和列表item样式,就可以快速定制各种列表;改变搜索及时刷新,分页控制,多选控制,周期更新,条件记忆等逻辑,完全不用关心了,ListFrame 可灵活配置开启。
这也算是我花了很多心思的得意之作了[憨笑],很多设计思路一直想分享出来(技术上没什么,都是正常的vue技术),但真正写的时候却无从下笔 —— 最不好讲的就是设计了,这是个需要靠领悟的东西,所以我就选一个最好讲的点——定时刷新列表的实现来分享了!
通常的做法
定时更新数据,核心是 setInterval 创建一个定时器,通常我们是这么做的:
1、当组件加载时创建一个定时器,定期调用请求接口;当组件销毁时清理掉这个定时器。这是两个不同的 hook,需要一个状态变量 timmer 记录定时器 id 。
2、由于用户可能点击搜索等 主动触发页面更新,所以我们需要做一个防抖,需要一个变量 lastReqTime 记录上次的更新时间,然后基于这个时间在定时刷新。
3、外界需要选择是否开启定期刷新,所以有一个boolean类型的prop
核心代码如下:
props: {
... ... // 其他代码省略 autoRefresh: { type: Boolean, default: false } }, data() { return {
... ... // 其他代码省略 lastReqTime: 0, timmer: null }; }, methods: {
... ... // 其他方法省略 // 分页请求数据的method: async initPage(pageNo = 1) { this.lastReqTime = Date.now(); ... ... // 其他代码省略 this.lastReqTime = Date.now(); } }, mounted() { if (this.autoRefresh) { this.timmer = setInterval(() => { if (Date.now() >= this.lastReqTime + 30000) { // 每30秒更新一次列表 this.initPage(this.pageNo); } }, 1000); // 每秒中确定一次 }
... ... // 其他代码省略 }, beforeDestroy() { clearInterval(this.timmer); }
这段代码的最大问题是逻辑分散,而且为了一个定期更新数据,产生了两个状态,还污染了initPage方法。
别人要想看懂你是怎么实现的,要到处找逻辑,很不容易理解。如果这个组件只有一个功能还好,毕竟就一个功能,问题是还有很多其他功能。
更好的做法
代码是写给人看的(尽管是给机器执行的,但维护的还是人) 把逻辑尽可能放在一起(即所谓的内聚)很重要:(具体看下面代码中加粗的注释)
props: {
... ... // 其他props省略 autoRefresh: { type: Boolean, default: false } }, mounted() { if (this.autoRefresh) {
// 所有逻辑都在一起,其他人阅读时不用到处找代码了(整体抽取 或 移除都很方便)
// 下面lastReqTime 和 timmer 就是一个普通的局部变量了,不是data中的状态了 let lastReqTime = 0; const cycleTime = 30000; const tickTime = 1000;
// 使用代理模式 来记录lastReqTime 而非污染原来的 initPage : const oldFn = this.initPage; this.initPage = function(...params) { lastReqTime = Date.now(); // 必须return,保持一致 return oldFn(...params).then(res => { lastReqTime = Date.now(); return res; }); };
// 定时器的创建 与销毁 : const timmer = setInterval(() => { if (Date.now() >= lastReqTime + cycleTime) { this.initPage(this.pageNo); } }, tickTime); this.$once('hook:beforeDestroy', () => { // 通过这样监听! clearInterval(timmer); }); }
... ... // if结束后 的其他代码省略 }
对比一下,内聚后的代码是不是逻辑更好理解了呢 ?