我们说我有一个api端点
/categories
,它返回一个类别数组
[
{
id: '1'
name: 'category-1'
},
{
id: '2'
name: 'category-2'
}
...
]
和端点,用于检索类别items/:categoryId
中返回项目数组
[
{ id: '1', name: 'item-1' },
{ id: '2', name: 'item-2' }
...
]
在UI上,我显示了一个类别列表,我可以展开这些类别并延迟加载项目列表。 我希望能够扩展多个类别,并且需要能够添加,编辑和删除项目。
为这样的场景组织状态的最佳方法是什么?
目前我的州看起来像这样:
{
entities: {
categories: {
'1': {
id: '1'
name: 'category-1'
},
'2': {
id: '2'
name: 'category-2'
},
...
},
items: {
'1': {
id: '1'
name: 'item-1'
},
'2': {
id: '2'
name: 'item-2'
},
...
}
},
categories: {
ids: ['1', '2', ...],
isFetching: bool
error: null
},
itemsByCategory: {
'1': {
ids: ['1', '2',...]
isFetching: bool,
error: null
}
...
}
}
在itemsByCategory
中,键是类别的ID,如果尚未加载给定类别的项目,则itemsByCategory
上的密钥将不存在。
此解决方案有效,但有一些缺点。为了删除项目,我必须传递两个键(项目ID和类别ID)而不是项目ID(我也可以通过所有类别来查找项目,但它可能会变慢)。
我也不满意检查是否加载了给定类别的项目。 (首先我要检查是否在
itemsByCategory
上定义了具有类别ID的键),因此我的选择器变得有点复杂。
有没有更好的方法来塑造这种情况的状态?
答案 0 :(得分:0)
为了从类别中删除项目,需要传递类别ID和项目ID。这是因为它们是连接在一起的。
从您的特定用例中抽象出来,每次您想要更改两个逻辑实体之间的连接(建立新的逻辑实体或破坏现有实体)时,您必须以某种方式解决该连接。可以这样做:
回到您的用例意味着:
categoryByItem
地图,或在每个categoryId
中包含items
字段。item.id = item.id + '/' + category.id
。然后,只需将项目ID分成'/'即可获得类别。关于检查itemsByCategory
是否存在类别ID键的问题,为什么不填充此对象以及填充entities.categories
对象?每次加载新类别(例如,使用id categoryId
)时,在itemsByCategory
下categoryId
下创建一个空对象(或非空,但形状指示已卸载状态)钥匙。
另一种可能性,特别是如果你发现自己经常进行null
检查,就是使用idx helper function from Facebook.有了它的帮助你的选择器会是这样的:
const isItemsLoaded = (state, categoryId) => idx(state, _ => _.itemsByCategory[categoryId].isLoaded);
答案 1 :(得分:0)
如果category
是item
的属性,则每个项目都应具有categoryId
属性,仅存储对其的引用,并避免在任何地方复制类别。如果API调用返回的项目不包含您可以在获得API结果后立即分配的类别,因为您知道该类别,因为您已经使用它来首先检索项目
categories
和itemsByCategory
都会通过存储元素的ID来复制entites
中已有的信息,您不需要存储这些信息。
如果您懒得加载的唯一内容是每个类别的项目,请在categories
中创建一系列已加载的类别,以检查您是否必须进行API调用。
itemsByCategory
对我来说看起来毫无用处,除非您还懒得加载有关每件商品的更多信息。
我不担心循环遍历所有项目以检索与每个类别对应的项目,只要您只对那些已展开的类别执行此操作即可。毕竟React意味着以这种方式使用并且非常擅长它。如果您遇到性能问题,可能是因为您正在更改父组件并强制重新呈现所有列表。
如果所有这些都失败了,您只需要渲染太多数据,并且可能会重新考虑UI并添加某种类型的分页。
另一个选项是在折叠类别后从entities
清除项目,并从已加载的类别中删除该类别,但这可能会对您的用户产生负面影响。