列表页面数据请求设计2

handleList()

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

示例

js 复制代码
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,判断当次请求拿到的数据量是否小于请求的数量。

代码大概变成这样了:

js 复制代码
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告知你前端了。

二、产品列表页面

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

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

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

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

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

js 复制代码
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也解决不了问题。比如按权重值排序,某产品已经被展现了,后来该产品权重值有下调,导致请求后续页面的时候数据重复。

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

相关推荐
恋猫de小郭几秒前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端