likes
comments
collection
share

列表页面数据请求设计2

作者站长头像
站长
· 阅读数 20

handleList()

处理成功返回的列表数据。一般接口数据不能直接满足页面渲染要求,需要做一些字段映射,逻辑处理等。

示例

const typeMap = {
  A: '类型A',
  B: '类型B',
  C: '类型C',
};

function handleList(list) {
  return list.map(item => {
    const { Type, Amount } = item;
    // 字段映射
    const typeName = typeMap[Type];
    // 金额格式化处理
    const amountFormatted = formatAmount(Amount);
    return Object.freeze({
      ...item,
      typeName,
      amountFormatted
    });
  });
}

需要特别指出的是,一般可以使用Object.freeze()对数据进行冻结,节省性能。

数据是否全部返回

使用state.list.length >= Total来判断数据是否全部返回。

但有时服务端会对Total使用缓存,只有查询第一页数据才会更新Total。如果数据发生删除,则导致实际数据量小于Total值。

为了保险,多加一层判断list.length < params.QueryNumber,判断当次请求拿到的数据量是否小于请求的数量。

代码大概变成这样了:

const actions = {
  queryList({ commit, state }, params) {
    // ...
    return API.getList(params)
      .then(rsp => {
        const { Total, List } = rsp.body;
        // 这里对数据进行处理
        const list = handleList(List);
        // ...
        // 返回是否全部加载完成
        return list.length < params.QueryNumber || state.list.length >= Total;
      })
      // 将错误处理成具体的格式(给组件使用)并返回错误
      .catch(handleError);
  },
};

服务端返回Finished字段

相较于前端来判断是否已返回全部数据,显然服务端更清楚,由服务端返回也更合适。

一些不寻常列表分页需求

一、用户产品收藏页

展示用户收藏的产品,但不展示当中不可购买的产品(有些产品因为是特定人群才能购买,或某些时间段才能购买等),默认按照用户的置顶或收藏时间倒序展示。

当前端请求第一页10条数据时,服务端的会按照排序取用户收藏的前10条数据,然后再做不可购的实时判断进行过滤,可能10条数据会被过滤得只剩7、8条了,返回给前端,前端持续请求下一页补齐10条(多余的数据暂存起来,不展示给用户)。

这种情况下数据是否请求完毕,只能靠服务端返回Finished告知你前端了。

二、产品列表页面

可购买的产品放在前面,不可购产品放置在尾部。

由于是否可购买是根据用户属性和产品属性决定的,进行实时查询的话,计算量较大,并且由于变化属性太多导致分页数据容易错乱(跳过某些产品或出现重复)。

服务端会在用户访问第一页时,将所有产品按产品和用户属性做好是否可购买的划分并做好缓存,后续分页请求都会从该缓存中读取。请求第一页会重新计算并做好缓存。

列表页面数据请求设计2

不可购的产品与可购产品之间还会展示所有不可购的标签,用户可以选择标签来筛选特定不可购的产品。

前端的做法是将BeginNumber的偏移设置为可购产品数量,然后做分页查询,页面呈现的效果类似于页面刷新。

const state = {
  list: [],
  total: 0,
  params: {
    BeginNumber: 0,
    QueryNumber: 10,
    Sort: 0,
    Filter: 0,
    NotBuy: ''
  }
};

const getters = {
  // 第一条不可购产品index
  firstNotBuyIndex(state) {
    return state.list.findIndex(item => item.NotBuy !== '');
  }
};

const actions = {
  queryList({ commit, state }, params) {
    // 将当前请求参数保存
    // 这样当页面刷新时可以使用当前的排序或过滤
    commit('updateParams', params);
    return API.getList(params)
      .then(rsp => {
        const { Total, List } = rsp.body;
        // 这里对数据进行处理
        const list = handleList(List);
        commit('updateTotal', Total);
        // 请求第一页
        if (params.BeginNumber === 0) {
          commit('updateList', list);
        }
        // 非第一页,则追加数据
        else {
          commit('updateList', state.list.concat(list));
        }
        // 返回是否全部加载完成
        return state.list.length >= Total;
      })
      // 将错误处理成具体的格式(给组件使用)并返回错误
      .catch(handleError);
  },
  // 请求第一页数据
  refreshPage({ dispatch }, params = {}) {
    params.BeginNumber = 0;
    params.NotBuy = '';
    return dispatch('queryList', params);
  },
  // 查询特定的不可购产品
  queryNotBuy({ getters, dispatch }, notBuy) {
    // 与refreshPage不一致的地方,不需要将页面滚动到顶部
    PageListBus.dispatch('beforeRefresh', { noScroll: true });
    const params = {
      NotBuy: notBuy,
      BeginNumber: getters.firstNotBuyIndex
    };
    return dispatch('queryList', params)
      .then(finished => {
        PageListBus.dispatch('afterRefresh', finished);
      })
      .catch(error => {
        PageListBus.dispatch('afterRefresh', { error });
      });
  }
};

注意点

再提一下列表分页实时查询存在的问题,那就是数据是动态的,可能有增有减。

若用户访问由产品添加时间倒序的产品列表,已展现第一页数据后管理端又新添一条产品。当用户请求第二页产品数据时,实时查询的话,将导致第二页的第一条数据是上一页的最后一条数据。

这是因为我们使用的是BeginNumber这种方式查询的。针对动态数据更推荐的做法是使用LastId的方式去查询。

有时候LastId也解决不了问题。比如按权重值排序,某产品已经被展现了,后来该产品权重值有下调,导致请求后续页面的时候数据重复。

需要根据具体情况来决定是否实时查询。

转载自:https://juejin.cn/post/7278602751703285771
评论
请登录