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

新手上路,请多包涵

Designing the State Shape 一章中,文档建议将您的状态保存在一个由 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',
  }]

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

{
   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。

使用 object-keyed-by-id 方法(以及自由使用扩展语法),reducer 的 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-keyed-by-id 方法的动机?这种状态形状还有其他优点吗?

2)似乎对象键控的方法使得处理 API 的标准 JSON 输入/输出变得更加困难。 (这就是为什么我首先使用对象数组的原因。)所以如果你使用这种方法,你是否只使用一个函数在 JSON 格式和状态形状格式之间来回转换它?这看起来很笨拙。 (尽管如果您提倡这种方法,那么您的部分推理是否比上面的对象数组缩减器更简单?)

  1. 我知道 Dan Abramov 将 redux 设计为理论上与状态数据结构无关(正如 “按照惯例,顶级状态是一个对象或其他一些键值集合,如 Map,但从 _技术上讲它可以是任何类型_,” 强调我的)。但是考虑到上述情况,是否只是“建议”将其保留为由 ID 键入的对象,或者是否存在其他无法预料的痛点我将通过使用使其成为对象的数组来运行,以至于我应该中止它计划并尝试坚持使用 ID 键控的对象?

原文由 nickcoxdotme 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 271
2 个回答

Q1:reducer 的简单性是因为不必搜索数组来找到正确的条目。不必搜索数组是优点。选择器和其他数据访问器可能并且经常通过 id 访问这些项目。每次访问都必须搜索数组成为性能问题。当您的阵列变大时,性能问题会急剧恶化。此外,随着您的应用程序变得越来越复杂,在更多地方显示和过滤数据,问题也会变得更加严重。这种组合可能是有害的。 By accessing the items by id , the access time changes from O(n) to O(1) , which for large n (here array items) makes一个巨大的差异。

Q2:您可以使用 normalizr 来帮助您从 API 到存储的转换。从 normalizr V3.1.0 开始,您可以使用 denormalize 走另一条路。也就是说,应用程序通常更多地是消费者而不是数据生产者,因此通常更频繁地转换为存储。

Q3:您使用数组遇到的问题与其说是存储约定和/或不兼容性问题,不如说是性能问题。

原文由 DDS 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

这是关键的想法。

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

  2. 与 API 的连接不应影响您的存储和缩减器的架构,这就是您采取行动以保持关注点分离的原因。只需将您的转换逻辑放入和取出 API 的可重用模块中,将该模块导入到使用 API 的操作中,就可以了。

  3. 我对具有 ID 的结构使用了数组,这些是我遭受的无法预料的后果:

  • 在整个代码中不断地重新创建对象
  • 将不必要的信息传递给 reducer 和 actions
  • 因此,糟糕的、不干净的和不可扩展的代码。

我最终改变了我的数据结构并重写了很多代码。 你已经被警告过,请不要给自己惹麻烦。

还:

  1. 大多数带有 ID 的集合都旨在使用 ID 作为对整个对象的引用,你应该利用它。 API 调用将获取 ID ,然后 是其余参数,您的操作和缩减器也是如此。

原文由 Marco Scabbiolo 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题