Redux + 重新选择:防止在 GPS 数据更新时重新计算选择器



最近,我一直在使用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 或名称发生变化时才会从数据库推送更改,但这是我希望远离的解决方案,因为它会导致许多相同的数据存储在商店中多次。

我尝试了以下方法:

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

如果有人需要更多信息或澄清,请随时询问。

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

不可变数据更新的标准方法要求,如果更新了嵌套字段,则还应复制和更新树中的所有祖先。 在您的示例中,对dog[3].gps的更新需要对gps数组、dog[3]dogsdata进行新的引用。 因此,使用此数据结构,对gps字段的任何更新都必须导致整个链上的新引用,因此 UI 将看到新的引用并假设它需要重新呈现。

一些可能的建议:

  • 编写一个选择器,通过 ID 查找狗条目,去除gps字段,然后对先前的值进行某种浅相等性检查,以查看是否有任何非gps字段是否实际更改,以便仅当其中一个字段不同时才返回新的"狗条目减去gps"对象。
  • 将 GPS 值存储在按 ID 键控的单独查找表中,而不是嵌套在狗条目本身中,以便对 GPS 数组的更新不会导致新的狗条目引用。

注意:由于注释中详细说明的原因,这不起作用

如果你不需要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}))
)

最新更新