redux:状态为对象的数组与对象的按键

在“ devise状态形状 ”一章中,文档build议将您的状态保持在ID为键的对象中:

将存储有ID的对象中的每个实体都保存为键,并使用ID从其他实体或列表中引用它。

他们继续说

把应用程序的状态想象成一个数据库。

我正在处理状态形状的filter列表,其中一些将被打开(它们显示在popup窗口中),或者有选定的选项。 当我读到“将应用程序的状态视为数据库”时,我考虑将它们视为JSON响应,因为它将从API(它本身由数据库支持)返回。

所以我把它想成是

[{ id: '1', name: 'View', open: false, options: ['10', '11', '12', '13'], selectedOption: ['10'], parent: null, }, { id: '10', name: 'Time & Fees', open: false, options: ['20', '21', '22', '23', '24'], selectedOption: null, parent: '1', }] 

但是,文档build议的格式更像

 { 1: { name: 'View', open: false, options: ['10', '11', '12', '13'], selectedOption: ['10'], parent: null, }, 10: { name: 'Time & Fees', open: false, options: ['20', '21', '22', '23', '24'], selectedOption: null, parent: '1', } } 

从理论上讲,只要数据是可序列化的(在“状态”标题下)就没有关系 。

所以,我愉快地用对象数组的方法,直到我写我的reducer。

通过对象键控的id方法(以及扩展运算符的自由使用),减速器的OPEN_FILTER部分变成

 switch (action.type) { case OPEN_FILTER: { return { ...state, { ...state[action.id], open: true } } } 

而使用对象数组的方法,它是更详细的(和辅助函数依赖)

 switch (action.type) { case OPEN_FILTER: { // relies on getFilterById helper function const filter = getFilterById(state, action.id); const index = state.indexOf(filter); return state .slice(0, index) .concat([{ ...filter, open: true }]) .concat(state.slice(index + 1)); } ... 

所以我的问题有三个:

1)reducer的简单性是采用object-key-by-id方法的动机吗? 这种状态还有其他的好处吗?

2)似乎对象键控的ID方法使得处理API的标准JSONinput/输出变得困难。 (这就是为什么我首先使用了对象数组。)所以,如果你使用这种方法,你是否只是使用一个函数在JSON格式和状态格式之间来回转换呢? 这似乎笨重。 (虽然如果你主张这种方法,那么你的推理的一部分就比上面的对象减速器不那么笨重了?)

3)我知道丹·阿布拉莫夫(Dan Abramov)在理论上将reduxdevise为状态数据结构不可知的(正如“按照惯例,顶层状态是一个对象或像Map这样的其他关键值集合,但是从技术上讲它可以是任何types “,重点是我的)。 但是鉴于上述情况,只是build议将它保留为ID所对象的对象,或者还有其他一些无法预料的痛点,我将通过使用一组对象来实现它,使得我应该放弃计划,并试图坚持一个ID的对象?

Q1:Reducer的简单性是不必search数组来查找正确条目的结果。 无需searcharrays是优势。 select器和其他数据访问器可能经常通过id访问这些项目。 不得不search每个访问的数组成为一个性能问题。 当你的arrays变大,性能问题急剧恶化。 此外,随着您的应用程序变得更加复杂,在更多地方显示和过滤数据,问题也会恶化。 这种组合可能是有害的。 通过访问id的项目,访问时间从O(n)更改为O(1) ,这对大n (这里是数组项目)有很大的不同。

Q2:你可以使用normalizr来帮助你从API转换到商店。 从normalizr V3.1.0开始,你可以使用denormalize去反过来。 也就是说,应用程序往往比数据的生产者更多的消费者,因此转换到商店通常会更频繁地进行。

问题3:使用数组将遇到的问题与存储约定和/或不兼容性问题没有太大的关系,而是更多的性能问题。

把应用程序的状态想象成一个数据库。

这是关键的想法。

1)具有唯一ID的对象允许您在引用对象时始终使用该ID,因此您必须在动作和缩减器之间传递最小数量的数据。 这比使用array.find(…)更高效。 如果你使用数组的方法,你必须传递整个对象,并且可能很快就会变得混乱,最终可能会在不同的reducer,actions,甚至是容器中(不要这样做)重新创build对象。 视图总是能够得到完整的对象,即使它们的关联reducer只包含ID,因为当映射状态时,你会得到集合的地方(视图获取整个状态来映射到属性)。 由于我所说的所有事情,行动最终有最小的参数,并减less最小的信息量,试一试,尝试这两种方法,你会看到体系结构更可扩展和干净利用ID如果集合有ID。

2)与API的连接不应该影响存储和还原器的体系结构,这就是为什么你需要采取行动来保持关注点分离的原因。 只需将您的转换逻辑放入可重用模块的API中,然后将该模块导入使用API​​的操作中即可。

3)我使用数组作为ID的结构,这是我遭受的不可预料的后果:

  • 重新创build对象不断严格的代码
  • 将必要的信息传递给reducer和action
  • 作为后续,糟糕,不干净,不可扩展的代码。

我最终改变了我的数据结构并重写了很多代码。 你已经受到警告,请不要让自己陷入困境。

也:

4)带ID的大多数集合都是用ID作为整个对象的引用,你应该利用它。 API调用将获得ID ,然后是其余的参数,所以您的动作和减速器。

1)reducer的简单性是采用object-key-by-id方法的动机吗? 这种状态还有其他的好处吗?

您希望保持以ID(键也称为标准化 )存储的对象中的实体的主要原因是,使用深度嵌套的对象 (通常从更复杂的应用程序中的REST API获取)非常麻烦 -既为您的组件和您的减速器。

用你当前的例子说明规范化状态的好处是有点困难的(因为你没有深度嵌套的结构 )。 但是让我们说,这个选项(在你的例子中)也有一个标题,并且是由系统中的用户创build的。 这会让响应看起来像这样:

 [{ id: 1, name: 'View', open: false, options: [ { id: 10, title: 'Option 10', created_by: { id: 1, username: 'thierry' } }, { id: 11, title: 'Option 11', created_by: { id: 2, username: 'dennis' } }, ... ], selectedOption: ['10'], parent: null, }, ... ] 

现在让我们假设你想创build一个组件来显示已经创build了选项的所有用户的列表。 要做到这一点,你首先必须请求所有的项目,然后遍历每个选项,最后得到created_by.username。

更好的解决办法是将响应归一化为:

 results: [1], entities: { filterItems: { 1: { id: 1, name: 'View', open: false, options: [10, 11], selectedOption: [10], parent: null } }, options: { 10: { id: 10, title: 'Option 10', created_by: 1 }, 11: { id: 11, title: 'Option 11', created_by: 2 } }, optionCreators: { 1: { id: 1, username: 'thierry', }, 2: { id: 2, username: 'dennis' } } } 

有了这个结构,列出所有已经创build了选项的用户(我们把它们分离在entities.optionCreators中,所以我们只需要遍历这个列表)就容易得多,效率也更高。

例如,为ID为1的筛选项创build选项的用户名也很简单:

 entities .filterItems[1].options .map(id => entities.options[id]) .map(option => entities.optionCreators[option.created_by].username) 

2)似乎对象键控的ID方法使得处理标准的JSONinput/输出API变得困难。 (这就是为什么我首先使用了对象数组。)所以,如果你使用这种方法,你是否只是使用一个函数在JSON格式和状态格式之间来回转换呢? 这似乎笨重。 (虽然如果你主张这种方法,那么你的推理的一部分就比上面的对象减速器不那么笨重了?)

JSON响应可以使用例如normalizr进行标准化 。

3)我知道丹·阿布拉莫夫(Dan Abramov)在理论上将reduxdevise为状态数据结构不可知的(正如“按照惯例,顶层状态是一个对象或像Map这样的其他关键值集合,但是从技术上讲它可以是任何types“,重点是我的)。 但是鉴于上述情况,只是build议将它保留为ID所对象的对象,或者还有其他一些无法预料的痛点,我将通过使用一组对象来实现它,使得我应该放弃计划,并试图坚持一个ID的对象?

这可能是一个build议更复杂的应用程序与大量深度嵌套的API响应。 不过,在你的例子中,这并不重要。

Interesting Posts