当前位置: 首页 > 知识库问答 >
问题:

状态为对象数组与由id设置关键帧的对象

南宫天逸
2023-03-14

在“设计状态形状”一章中,文档建议将您的状态保留在由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'],
    selectedOpthtml" target="_blank">ion: null,
    parent: '1',
  }
}

理论上,只要数据是可序列化的(在“状态”标题下),它就不重要了。

所以我很高兴地采用了对象数组的方法,直到我编写了我的还原器。

通过id键控对象的方法(以及扩展语法的自由使用),OPEN\u过滤器是reducer的一部分

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)减速器的简单性是否是采用对象键控方式的动机?这种状态还有其他优势吗?

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

3) 我知道Dan Abramov将redux设计为理论上不可知的状态数据结构(正如“按照惯例,顶级状态是一个对象或一些其他键值集合,如地图,但技术上它可以是任何类型,”emphasis mine指出)。但是考虑到上述情况,是否只是“建议”将其保留为ID键控的对象,或者使用一组对象使其成为我应该中止该计划并尝试坚持使用ID键控的对象时,是否会遇到其他无法预见的痛点?

共有3个答案

越伟泽
2023-03-14

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

您希望将实体保留在以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,
},
...
]

现在让我们假设您想要创建一个组件,该组件显示已创建选项的所有用户的列表。要做到这一点,您必须首先请求所有项目,然后迭代它们的每个选项,最后获得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'
    }
  }
}

有了这个结构,列出所有已经创建选项的用户会更容易,也更高效(我们在entities.option创建者中隔离了他们,所以我们只需要循环浏览那个列表)。

显示那些为ID为1的筛选项创建选项的用户名也很简单:

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

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

JSON响应可以使用normalizer等进行规范化。

3) 我知道Dan Abramov将redux设计为理论上不可知的状态数据结构(正如“按照惯例,顶级状态是一个对象或一些其他键值集合,如地图,但技术上它可以是任何类型,”emphasis mine指出)。但是考虑到上述情况,是否只是“建议”将其保留为ID键控的对象,或者使用一组对象使其成为我应该中止该计划并尝试坚持使用ID键控的对象时,是否会遇到其他无法预见的痛点?

这可能是对具有大量深度嵌套API响应的更复杂应用程序的建议。不过,在您的特定示例中,这其实并不重要。

苏乐
2023-03-14

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

这是关键的想法。

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

2)与API的连接不应该影响存储和减速器的体系结构,这就是为什么你有操作来保持关注点的分离。只需将转换逻辑放入和移出可重用模块中的API,将该模块导入使用该API的操作中,就应该如此。

3) 我将数组用于带有ID的结构,这些是我所遭受的无法预料的后果:

  • 在整个代码中不断重新创建对象
  • 向减速器和动作传递不必要的信息
  • 因此,代码不好、不干净、不可伸缩

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

也:

4)大多数带有ID的集合都是为了使用ID作为对整个对象的引用,你应该利用这一点。API调用将获得ID,然后是其余的参数,操作和减速器也是如此。

应安国
2023-03-14

问题1:reducer的简单性是因为不必搜索数组来找到正确的条目。不必搜索整个阵列是优势。选择器和其他数据访问器可能而且经常通过id访问这些项目。必须在阵列中搜索每次访问,这将成为一个性能问题。当阵列变大时,性能问题会急剧恶化。此外,随着你的html" target="_blank">应用程序变得越来越复杂,在更多的地方显示和过滤数据,问题也变得更加严重。这种组合可能是有害的。通过使用id访问项目,访问时间从O(n)更改为O(1),这对于大型n(此处为数组项目)而言会产生巨大的差异。

问题2:您可以使用normalizer帮助您将API转换为存储。从normalizer V3.1.0开始,您可以使用反规范化来实现另一个目的。这就是说,应用程序的消费者往往多于数据生产者,因此,向存储的转换通常更频繁。

问题3:使用阵列时遇到的问题与其说是存储约定和/或不兼容的问题,不如说是性能问题。

 类似资料:
  • 问题内容: 对于我正在使用的插件,我必须具有如下所示的状态: 我如何在不设置其余访问权限的情况下设置hospital_id的状态? 这似乎删除了除了hospital_id之外的所有内容: 问题答案: 您有几种选择: 借助ECMA6,您可以使用对象传播建议()创建具有更新属性的对象的副本。 您可以在对象()上使用本机分配功能 或为最短的版本和原子更新: 还有一个选项是更新插件: 我建议使用第一个。

  • `正在尝试更新onclick中的对象数组。我有两个状态,即数组格式的“IntialValue”和“Todos”。当我检查react developer工具时,我可以看到有两种状态。该任务的流程是用户可以在输入文本框中输入文本,一旦用户点击了添加按钮,我想把它添加为对象数组,我保留了一个布尔字段和Id字段。我面临的问题是我能正确地得到“InputValue”。我还得到了对象的数组,但不是在“todo

  • 问题内容: 假设您有一个非常简单的数据结构: …并且您想将其中一些存储在javascript变量中。如我所见,您有三个选择: 如果您要存储(或希望可能拥有)多个“价值”部分(例如,增加他们的年龄等),显然第二或第三种选择是可行的,因此,为了论证,让我们假设在此结构中再也不需要任何数据值了。您选择哪一个,为什么? 编辑 :该示例现在显示最常见的情况:非顺序ID。 问题答案: 每个解决方案都有其用例。

  • 对于我正在使用的插件,我必须具有如下状态: 如何在不设置其余访问权限的情况下设置hospital_id状态? 这似乎删除了一切,但hospital_id:

  • 问题内容: 问题在于确定以下符号之间的权衡: 基于JSON : 基于数组 : 关于同一问题的这篇文章,我已经决定(在前端)使用JSON对象表示法而不是对象数组,因为它符合我的要求,更好的性能和更少的浏览器代码。 但是问题在于列表本身不是静态的。我的意思是,该列表正在生成,即从DB(NoSQL)获取/存储,并通过服务器上的JavaAPI为新条目创建。我无法决定在后端应使用哪种表示法(最终也会影响UI

  • 我正在寻找规范化的一些数据从API返回。已经在项目中使用下划线。 我希望创建一个如下所示的数据结构: 再次感谢所有的答案!