状态为对象数组与由id键入的对象

nic*_*tme 87 javascript spread-syntax redux

设计状态形状的章节中,文档建议将您的状态保存在由ID键入的对象中:

将每个实体保存在以ID作为密钥存储的对象中,并使用ID从其他实体或列表中引用它.

他们继续陈述

将应用程序的状态视为数据库.

我正在处理状态形状以获取过滤器列表,其中一些将打开(它们显示在弹出窗口中),或者已选择选项.当我读到"将应用程序的状态视为数据库"时,我考虑将它们视为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',
  }]
Run Code Online (Sandbox Code Playgroud)

但是,文档建议更像一种格式

{
   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',
  }
}
Run Code Online (Sandbox Code Playgroud)

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

所以我愉快地使用了对象阵列方法,直到我写了我的减速器.

使用object-keyed-by-id方法(以及扩展语法的自由使用),OPEN_FILTERreducer 的一部分变为

switch (action.type) {
  case OPEN_FILTER: {
    return { ...state, { ...state[action.id], open: true } }
  }
Run Code Online (Sandbox Code Playgroud)

使用对象数组方法,它更冗长(和辅助函数依赖)

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));
    }
    ...
Run Code Online (Sandbox Code Playgroud)

所以我的问题有三个:

1)减速器的简单性是否采用对象键控id方法的动机?这种状态还有其他优点吗?

2)看起来像object-keyed-by-id方法使得处理API的标准JSON输入/输出变得更加困难.(这就是我首先使用对象数组的原因.)因此,如果你采用这种方法,你是否只使用一个函数在JSON格式和状态形状格式之间来回转换它?这看起来很笨拙.(虽然如果你提倡这种方法,你的推理是否比上面的对象数组减少器更少笨重?)

3)我知道Dan Abramov设计的redux在理论上是状态数据结构不可知的(如"惯例"所示,顶级状态是一个对象或一些其他键值集合,如Map,但从技术上讲它可以是任何类型,"强调我的".但鉴于上述情况,是否只是"建议"将其保持为ID键入的对象,或者是否存在其他无法预料的痛点,我将通过使用一系列对象来解决这个问题,这样我就应该中止计划并尝试坚持使用ID键入的对象?

DDS*_*DDS 42

Q1:reducer的简单性是不必搜索数组以找到正确的条目.不必搜索阵列是有利的.选择器和其他数据访问器可以并且通常可以访问这些项目id.必须在阵列中搜索每次访问都会成为性能问题.当阵列变大时,性能问题会急剧恶化.此外,随着您的应用变得更加复杂,在更多地方显示和过滤数据,问题也会恶化.这种组合可能是有害的.通过访问项目id,访问时间从更改O(n)O(1),对于大型n(此处为数组项目),这会产生巨大差异.

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

问题3:使用数组遇到的问题不是存储约定和/或不兼容问题,而是更多的性能问题.


Mar*_*olo 11

将应用程序的状态视为数据库.

这是关键的想法.

1)具有唯一ID的对象允许您在引用对象时始终使用该id,因此您必须在actions和reducers之间传递最小数量的数据.它比使用array.find(...)更有效.如果使用数组方法,则必须传递整个对象并且很快就会变得混乱,最终可能会在不同的reducers,actions或甚至容器中重新创建对象(您不希望这样).视图将始终能够获取完整对象,即使它们的关联reducer仅包含ID,因为在映射状态时,您将获得集合(视图获取整个状态以将其映射到属性).由于我所说的所有内容,操作最终只有最小量的参数,并且减少了最小的信息量,试一试,尝试这两种方法,你会发现架构最终更具可扩展性和清洁性如果集合具有ID,则为ID.

2)与API的连接不应影响存储和缩减器的体系结构,这就是为什么要采取措施来保持关注点的分离.只需将转换逻辑放入和放出API中的可重用模块,在使用API​​的操作中导入该模块,应该是它.

3)我使用带有ID的结构的数组,这是我所遭受的不可预见的后果:

  • 重新创建对象使代码变得严格
  • 将不必要的信息传递给减速器和动作
  • 由此可见,坏,不干净,不可扩展的代码.

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

也:

4)大多数带ID的集合都是使用ID作为整个对象的引用,你应该利用它.API调用将获取ID ,然后获取其余参数,因此您的操作和减少器也将获得.

  • 我遇到一个问题,我们有一个应用程序,其中包含大量数据(1000 到 10,000 条),这些数据按 id 存储在 redux 存储中的一个对象中。在视图中,它们都使用排序数组来显示时间序列数据。这意味着每次重新渲染完成时,它都必须获取整个对象,转换为数组,然后对其进行排序。我的任务是提高应用程序的性能。在这种用例中,将数据存储在排序数组中并使用二分搜索而不是对象来进行删除和更新更有意义吗? (2认同)

tob*_*sen 8

1)减速器的简单性是否采用对象键控id方法的动机?这种状态还有其他优点吗?

您希望将实体保存在以ID作为键存储的对象(也称为规范化)中的主要原因是,使用深层嵌套对象(这是您通常从更复杂的应用程序中的REST API获取的内容)非常麻烦-适用于您的组件和减速器.

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

[{
  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,
},
...
]
Run Code Online (Sandbox Code Playgroud)

现在假设您要创建一个组件,该组件显示已创建选项的所有用户的列表.要做到这一点,首先必须请求所有项目,然后遍历每个选项,最后获取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'
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

使用这种结构,列出已创建选项的所有用户(我们将它们隔离在entities.optionCreators中,因此我们只需循环遍历该列表)就更容易,也更有效.

显示例如已为ID为1的过滤器项创建选项的用户名也很简单:

entities
  .filterItems[1].options
  .map(id => entities.options[id])
  .map(option => entities.optionCreators[option.created_by].username)
Run Code Online (Sandbox Code Playgroud)

2)看起来像object-keyed-by-id方法使得处理API的标准JSON输入/输出变得更加困难.(这就是我首先使用对象数组的原因.)因此,如果你采用这种方法,你是否只使用一个函数在JSON格式和状态形状格式之间来回转换它?这看起来很笨拙.(虽然如果你提倡这种方法,你的推理是否比上面的对象数组减少器更少笨重?)

可以使用例如normalizr来标准化JSON响应.

3)我知道Dan Abramov设计的redux在理论上是状态数据结构不可知的(如"惯例"所示,顶级状态是一个对象或一些其他键值集合,如Map,但从技术上讲它可以是任何类型,"强调我的".但鉴于上述情况,是否只是"建议"将其保持为ID键入的对象,或者是否存在其他无法预料的痛点,我将通过使用一系列对象来解决这个问题,这样我就应该中止计划并尝试坚持使用ID键入的对象?

这可能是对具有大量嵌套API响应的更复杂应用的推荐.但是在你的特定例子中,它并不重要.

  • `map` 返回 undefined,如 [here](http://stackoverflow.com/q/41794393/3125070),如果资源是单独获取的,则使 `filter` 的方式过于复杂。有解决办法吗? (2认同)