列表页面的总结与思考一

158 阅读5分钟

我们一般会使用ScrollViewInfiniteScrollPushLoad等组件来展示列表页面的数据。

但对于一个列表页面来说,仅仅是在列表页面有数据的状态下,才使用这些组件。

列表页面还有其它一些状态。

列表页面状态划分

  1. init - 初始状态
  2. noData - 无数据状态
  3. error - 出错状态
  4. list - 展示列表状态
  5. refresh - 刷新状态

init 状态

刚进入列表页面的状态,一般是正在发起第一页数据的请求。

此时页面应该呈现的是一个数据加载的效果(空白页面上一个大大的loading)。

noData 状态

查询第一页,返回没有数据,此状态也可以划分到error状态下。

此时页面应该反馈用户暂无数据,并引导用户去添加数据,或去其它地方发现数据(比如当前筛选下没有数据,可以试试全部)。

error 状态

查询接口失败或用户不满足条件的一个状态。

可能是服务器报错或用户网络问题。

如果是服务器报错,可以提示用户进行反馈或稍后再试;若是用户的网络问题,可以引导用户去设置网络。

list 状态

使用ScrollView等组件展示列表数据。

如果数据没有全部请求完毕,则提示用户上滑加载更多

refresh 状态

当页面有筛选条件或排序时,需要重新请求第一页。

与init状态的区别是,需要保持当前页面状态(如list状态),然后进入到另一个状态(noData/error/list)。并且如果页面状态是由list状态进入list状态,则需要重置组件的滚动高度。

列表展示组件

我们使用ScrollView之类的列表展示组件,来展示列表数据和进行分页数据请求触发。

列表展示组件状态划分(无下拉刷新效果)

  1. loadMore - 上滑加载更多
  2. loading - 加载中
  3. finished/noMore - 全部加载完成
  4. failed/error - 加载失败

loadMore 上滑加载更多

当组件展示了第一页数据后,若数据还没有全部请求完毕,则可以提示用户上滑加载更多,用户进行上滑后,触发请求下一页数据。

只要用户数据还没有全部请求完成,就可以提示用户上滑加载更多,引导用户操作触发请求下一页数据。

loading 加载中

用户上滑后(达到触发下一页请求的条件),则组件状态变为加载中,这个状态也是唯一的组件自身可以感知的状态。

finished/noMore 全部加载完成

当所有数据都请求完成时,反馈给用户没有更多数据了。

此时可以在下方引导用户去添加更多数据或者去其它地方查找数据(类似暂无数据的引导效果)。

failed/error 加载失败

用户根据引导上滑,请求下一页数据时加载失败。

可能的原因是服务器报错或用户本地网络错误,如果是服务器原因,可以提示用户再次尝试或反馈问题;若是用户网络问题则可以引导用户去设置网络。

设置组件的状态

组件的这些状态中,组件唯一可以感知的状态就是loading,其它几个状态需要我们根据接口的查询情况去手动设置。

通过load事件设置组件状态

一般组件会提供load事件(或类似事件),用来通知进行下一页请求,该方法需返回一个布尔值,用来标志全部数据是否请求完毕。

load事件触发时,此时组件已感知并进入到loading状态,当load事件执行完成后,根据返回值,组件进入loadMorefinished状态。

load事件执行报错,则组件进入到failed状态。

使用ref手动修改组件内部状态

若页面可以进行筛选、排序等操作时,一个load事件显然无法满足。

如,此时页面数据全部加载完成,用户再进行了排序操作(并非通过上滑触发),那么不会触发load事件。

此时需要手动更改组件的状态。给组件指定ref,然后调用组件内部修改状态的方法(优先)或直接修改状态值。

复杂列表页面

上面说过,复杂的列表页面有诸如筛选条件、排序,需要手动修改组件内部状态。

筛选组件、排序组件与列表组件不会总是在一个父组件中,筛选组件或排序组件无法直接拿到列表组件的ref来修改组件的状态。

一种方式是将事件或方法放在共有的祖先组件中,然后层层传递,但这种方式真的很不方便,也不利于组件的调整。

期望一种更加灵活方便的方式,可以让我们在任意地方(如组件/store/utils)中去通知列表页面自己来更改组件状态。

为列表页面建立事件总线(eventBus)

const ListPageBus = {
  // 所有的页面列表组件,如果一个页面有多个listPage组件,则需要使用key值进行区分管理
  comps = {},
  // 监听事件
  listen(comp, event, handler) {
    // 如果页面上只有一个pageList组件,无需设置key
    const { key = '' } = comp.$vnode;
    let compSaved = this.comps[key];
    // 组件没有被保存过或者组件被新建了
    if (!compSaved || comp !== compSaved) {
      this.comps[key] = { comp, events: {} };
      // 组件销毁时需要解绑监听的事件
      comp.$once('hook:beforeDestroy', () => {
        // 不能移除新的同名key的组件的消息
        if (comp === this.comps[key].comp) {
          delete this.comps[key];
        }
      });
    }
    const { events } = this.comps[key];
    let eventHandlers = events[event];
    if (!eventHandlers) {
      eventHandlers = events[event] = [];
    }
    // handler可以是一个函数或一个函数数组
    if (!Array.isArray(handler)) {
      handler = [handler];
    }
    // 防止相同的handler
    handler = handler.filter(h => !events[event].includes(h));
    // 订阅消息
    eventHandlers.push(...handler);
  },
  
  listenMany(comp, events) {
    Object.keys(events).forEach(event => {
      this.listen(comp, event, events[event]);
    });
  },
  
  // 消息通知
  dispatch(event, payload, key = '') {
    // 通过key拿到对应的组件和消息
    const opt = this.comps[key];
    if (!opt) return;
    const { comp, events } = opt;
    (events[event] || []).forEach(handler => {
      // 执行用户事件
      try {
        handler.call(comp, payload);
      } catch (e) {
        console.error(`[ListPageBus] 消息执行错误:`, e);
      }
    });
  }
};

列表页面的总结与思考二