【问题标题】:Redux + Reselect: Preventing selector recalculation when GPS data updatesRedux + Reselect:在 GPS 数据更新时防止选择器重新计算
【发布时间】:2018-06-14 16:06:16
【问题描述】:

最近,我一直在开发一个非常大的应用程序,使用 React+Redux 和 Reselect 来记忆数据并防止不必要的重新渲染,我遇到了一个我似乎无法解决的特定问题。

在 Redux 状态下,我将大量数据存储为由 id 索引的对象。这些对象都有一个属性(我们称之为 gps),它会随着实时 gps 坐标进行更新。

这些数据有两种用途。第一个是在地图上,其中 GPS 数据是相关的。第二个是在 UI 中,其中 GPS 数据不相关。任何时候更新任何对象的 GPS 数据时,该对象都会流入并在 Redux 存储中替换,这会更新我的选择器中该对象的引用。

Redux 存储示例:

data: {
   dogs: {
      1: {id: 1, name: "name1", gps: [123, 234]},
      2: {id: 2, name: "name2", gps: [123, 234]},
      3: {id: 3, name: "name3", gps: [123, 234]},
      4: {id: 4, name: "name4", gps: [123, 234]}
   }
}

例如 state.dogs[1].gps 中的数据可能每秒更新 3 到 5 次。这可能发生在 state.data.dogs 中的任何对象中。

选择器的写法如下:

const dogDataSelector = state => state.data.dogs;

const animalsSelector = createSelector(
    dogDataSelector,
    (dogs) => {
        return Object.keys(dogs).map(id => {
            return dogs[id];
        })
    }
)

现在,当我想要所有狗时,这段代码可以正常工作,因为它们会更新,包括 GPS。

我似乎无法弄清楚如何专门为不包括 GPS 更新的 UI 编写选择器。 100次中有99次,当狗更新时,它是GPS更新。 UI 根本不关心 GPS,但正因为如此,选择器会向前发送新数据,这会导致 UI 重新渲染。

我知道可以编写一个新流,仅在狗的 id 或名称更改时才从数据库推送更改,但这是我希望远离的解决方案,因为它会导致很多相同的数据要在存储中多次存储。

我尝试了以下方法:

  • 创建一个选择器,该选择器返回一个精简版的狗,只有 id 和 name,通过键存储在一个对象中。此选择器稍后用作输入选择器,但仍会导致不必要的选择器返回。
  • 创建一个只返回一组狗 ID 的选择器,并将其传递给 UI。深度相等检查用于防止重新渲染,id 数组用于从状态中提取特定的狗对象。这不是一个理想的解决方案,而且看起来很糟糕。

如果有人需要更多信息或说明,请随时提问。

更新:这是一个问题的主要原因之一是任何狗的任何 GPS 更新都会导致 dogDataSelector 返回一个新的参考。反过来,这将导致 animalsSelector 触发更新并返回一个新值。

【问题讨论】:

  • 我认为解决方案是自定义相等检查,使用createSelectorCreator - 请参阅github.com/reduxjs/…
  • @DavidKemp 这是我尝试过的一种解决方案,以及它目前的实施方式。它可以工作,但可能会更好,因为通过的数据量可以是 5000 多个项目,并且在这一点上,额外的渲染似乎比相等检查更便宜。不过,谢谢你的帮助!如果我不能实施更好的解决方案,这就是我将要采用的方法。

标签: reactjs data-structures redux reselect


【解决方案1】:

不可变数据更新的标准方法要求,如果一个嵌套字段被更新,它在树中的所有祖先也应该被复制和更新。在您的示例中,对dog[3].gps 的更新将需要对gps 数组、dog[3]dogsdata 的新引用。因此,使用此数据结构,对 gps 字段的任何更新必须会在整个链上产生新的引用,因此 UI 会看到新的引用并假设它需要重新渲染。

几个可能的建议:

  • 编写一个选择器,通过其 ID 查找狗条目,去掉 gps 字段,然后对先前的值进行某种浅等式检查,看看是否有非 gps 字段有实际上已更改,因此仅当其中一个字段不同时才返回新的“dog entry减gps”对象。
  • 将 GPS 值存储在以 ID 为键的单独查找表中,而不是嵌套在狗条目本身中,这样对 GPS 数组的更新不会导致新的狗条目引用。

【讨论】:

  • 我试图避免相等检查(至少是深度相等检查),因为 dog 对象可以包含 5000 多个项目。然而,这两种解决方案都非常有用!在编写选择器时,我需要一个每次都返回整个 state.data.dogs 对象的选择器。我的信息流将在 5000 多只狗中的任何一个更新任何内容(包括 GPS)时发送更新。您提出的解决方案在这种情况下仍然有效吗?是否可以编写一个可以从所有dogs 中提取 GPS 的选择器,然后在没有参考更新的情况下将其用作输入选择器?
  • 您的第二个解决方案似乎也非常好,从长远来看可能会更好。我可以更新我的后端以包含两个流;一个是 GPS 流,另一个是 name/id/whatever 流,并将这两个流存储在不同的位置。在我的地图上,获取 GPS 和信息,在 UI 中获取信息。
【解决方案2】:

注意:由于评论中详述的原因,这不起作用

如果您不需要 gps 数据,那么您当然可以将其剥离,reselect 会忽略它

类似:

const dogDataSelector = state => state.data.dogs;

const animalsSelector = createSelector(
    dogDataSelector,
    (dogs) => {
        return Object.keys(dogs).map(id => {
            return dogs[id];
        })
    },
    (dogsArray) => dogsArray.map(({id, name}) => ({id, name}))
)

【讨论】:

  • 这里的问题是,对 gps 数组进行适当的不可变更新会导致链上出现新的引用,从而导致此选择器每次都重新计算并且永远不会正确记忆。
  • @markerikson 是正确的,这就是我一直在努力解决的问题。 dogDataSelector 输入选择器将在每次 gps 对狗进行更新时显示更新,并导致 animalSelector 重新返回
  • @naturalrouge 您可以在问题中添加该信息吗?
  • @DavidKemp 肯定会的!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 2021-07-07
相关资源
最近更新 更多