在一个复杂的项目中,我们可以将Redux store看成为客户端的缓存数据库。
众所周知数据库的设计大有讲究,需要考虑到数据查找的性能,和可拓展性。作为前端“数据库”的Redux store也是同样的,存放的方式、位置不好会增加bug出现的几率、读写数据的复杂度、维护的成本。
那你说我们在数据层已经将数据处理了一遍,前端再加一层处理不是多此一举吗?的确,随着对用户体验的要求越来越高,而支持着这种极致体验的网络速度还无法跟上的时候,特殊手段的出现是必然的。
在这里,我想讨论几种我用到或者看到的实现方式,每种可能适应不同的场景。如果你有更好的想法,欢迎留下评论。
全局唯一的数据
比如当前登陆用户的一些必要信息。每次读取API都直接覆盖之前存的数据,特别简单直观。
store结构例子:
type MyStore = {
id: number,
name: string,
// ...
};
详情数据
这里的详情指通常是作为一个页面的主要数据存在的,没有这个数据页面就没法显示的这种。比如:博客里的博客内容,电商里的产品信息,商店信息等。
这类的数据通常都会有一个全局唯一的ID,我们就拿这个ID作为Object key来使用,如下:
type MyStoreById = {
[key: number]: {
id: number,
content: string,
// ...
}
列表数据
列表数据应该是最常见的了,比如:待办事项列表,博客文章列表,搜索结果列表等。
这类的数据,处理的复杂度就在于,你可能需要根据具体的业务逻辑和UX来设计存储的方式,而且通常要考虑分页的问题。
分页是指一个列表太长的时候,需要分成多页显示。目前市面上常见的逃不开到底/顶懒加载,和点击页码切换这两种UX(像瀑布流这种就是两者结合)。
最常见的做法是通过offset(直译为偏移,列表开始的位置),limit(列表的长度)向API请求一定数量的数据。API返回数据中带上一些必要的帮助前端判断有没有下一页或者一共多少页的信息,最常用的是列表总长。也就是说你在store里存的一般不会是很简单的一个Object数组,而可能是这样的:
type MyListStore = {
[key: string | number]: {
list: ListItem[],
total: number
}
};
那这个key用什么呢?答案也是不唯一的。我们要考虑的不仅仅是读写数据时的时间、空间复杂度,还有重用性,扩展性等等。
拿博客来说,通常博客的列表会根据栏目来分。这里其实有2种列表:栏目列表,和该栏目的博客列表。
拿每个栏目里的博客列表来说,有这个列表的前提是有这个栏目的ID对吧。所以就可以用栏目ID作为key。
但是别忘了上面讲的分页问题。下拉懒加载的可以就粗暴地使用单个数组,每次懒加载都往数组最后追加数据, 如下:
type Action = {
type: string,
key: number,
data: BlogList
};
type Blog = {
id: number,
title: string,
content: string
};
type BlogList = {
list: Blog[],
total: number
};
type ListReducerState = {
[key: number]: BlogList
};
export default function listReducer(
state: ListReducerState = {
},
action: Action
) {
switch (action.type) {
case ‘DATA_LOADED’: {
// API返回数据后执行
const {
key, data } = action;
if (!Array.isArray(data.list) || !data.list.length) {
return; }
const existingList = state[key] || {
};
const updatedList = {
// 往数组最后追加最新拉下来的数据
list: [...(existingList.list || []), ...data.list],
total: data.total
};
return {
...state,
[key]: updatedList
};
}
}
return state;