Redux:集合中项目的派生数据

7

以下是我状态树的简化版本:

{
    "radius": 8,
    "nodes": [
        { "id": 1, "x": 10, "y": 10 },
        { "id": 2, "x": 15, "y": 10 },
        { "id": 3, "x": 20, "y": 10 }
    ]
}

基本上,我有一个节点列表,每个节点都有 xy 坐标。我还有一个半径数字,用于计算哪些其他节点在节点的半径范围内,即密切相邻的节点。
我需要我的状态看起来像这样:
{
    "radius": 8,
    "nodes": [
        { "id": 1, "x": 10, "y": 10, "neighbors": [2] },
        { "id": 2, "x": 15, "y": 10, "neighbors": [1, 3] },
        { "id": 3, "x": 20, "y": 10, "neighbors": [2] }
    ]
}

计算邻居的成本相当昂贵,因此只有在节点位置发生更改时才需要计算。
我研究了使用选择器来解决这个问题,但我不确定选择器是否有效。为了推导出给定节点的邻居,我需要整个节点列表和半径。如果我将整个节点列表传递给选择器,选择器将在节点集合中的任何内容发生更改时重新计算。我只需要在任何节点的x或y值更改时重新计算。请注意,这些节点除了x和y之外还有很多其他键。
此外,似乎要使选择器工作,我需要为节点数组的每个元素创建一个新的选择器,而该数组是动态的。这是正确的吗?
另一个障碍是,我需要此邻居列表来计算其他reducers中的其他状态。这是否意味着我应该在节点reducer中进行此计算?
有人对这个问题有什么见解吗?
*编辑
最终我把邻居移到了状态中而不是推导它们。我需要从其他操作中访问邻居,并且我无法找到缓存/记忆它们的方法。
这似乎有点脆弱,因为我实际上是在某些涉及它们的操作中派生和存储邻居,并且在其他不涉及它们的操作中不重新派生它们。 (这几乎就是一个自动执行记忆化选择器的过程...)但遗憾的是,我找不到一个好的方法来做到这一点。
这样做有问题吗?
*编辑2
最终我将我的节点状态分为两部分:
nodes: {
    nodesById: {
        "1": { "id": 1, "color": "blue", ... },
        "2": { "id": 2, "color": "red", ... },
        ...
    },
    positionsById: {
        "1": { "id": 1, "x": 0, "y": 10 },
        "2": { "id": 2, "x": 10, "y": 10 },
        ...
    }
}

这样,我就能编写一个选择器,只需节点的位置和半径即可计算邻居:

export const getNeighborsById = createSelector(
    (positionsById, radius) => positionsById,
    (positionsById, radius) => radius,
    (positionsById, radius) => {
        // calculate neighbors
    }
);

positionsById改变时,此选择器仅重新运行,而不是nodesById改变(后者发生的次数更多)。

这解决了我的问题,但在同一个reducer中维护两个列表似乎有点不对,但也许不是...


我们正在面临完全相同的问题。你找到可用的模式了吗? - Anton Abilov
@AntonAbilov 我最终使用的解决方案在EDIT2中已经描述。它本质上是通过将实体分解(节点和节点位置)并将它们分别存储在由ID索引的字典中来规范化我的redux状态。 - lax4mike
2个回答

2
关于Redux中的状态,有几个常见的误解:
  1. 必须使用原生JS类型来描述状态。 错误
  2. 状态表示屏幕上显示的内容。 部分错误

1. 使用正确的数据结构

根据您的用例,将节点存储在Array中似乎不是最佳选择。是的,您可以这样做,但每当添加、删除或修改节点时,您都需要遍历列表并更新neighbors。如果将此过程移至选择器,则需要某种记忆化以不牺牲性能。

我认为您的节点应该存储在空间分区数据结构中(例如不可变四叉树)。这种数据结构会使节点遍历速度更快,并且很容易识别邻居。

2. 使用选择器派生状态

如果您需要对状态执行复杂的选择操作,或者在使用状态之前需要将其派生为另一种形式,则选择器非常有用。

如果您的节点存储在四叉树中,则可以编写一个选择器来查询其邻居并将它们作为数组返回。


这是使用四叉树的好主意,我想我会这样做。但我仍然需要一个解决方案来避免在每次更新时进行此计算。我正在考虑为“nodePosition”设置专用树,以及为节点的其他属性设置其他树。 - lax4mike
你可以使用 reselect 进行记忆化计算。 - Florent
我认为我不能使用 reselect,因为计算是针对集合中的每个节点进行的。Reselect 只会记忆最后一次计算。这是我一直在努力解决的问题。我是否应该使用另一种更传统的方法来进行记忆化?还是为集合中的每个节点创建一个新的 reselect 选择器? - lax4mike

0
这周我在我的应用程序中遇到了非常类似的问题,最终我在代表集合中每个项目的组件内创建了记忆化选择器。这样,我可以将每个项的特定数据提供给每个组件中的Reselect.createSelector,而不是尝试创建一个处理集合中所有元素的超级选择器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接