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

时间:2018-06-14 16:06:16

标签: reactjs data-structures redux reselect

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

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

此数据以两种方式使用。第一个是在地图上,GPS数据是相关的。第二个是UI,其中GPS数据不相关。每当在任何对象上更新GPS数据时,该对象将在Redux存储中流入并替换,Redux存储在选择器中更新该对象的引用。

Redux Store示例:

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

如果有人需要更多信息或澄清,请不要犹豫。

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

2 个答案:

答案 0 :(得分:1)

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

一些可能的建议:

  • 编写一个选择器,通过其ID查找狗条目,去掉gps字段,然后对前一个值进行某种浅等式检查,看看是否有非{{1实际上已经改变了字段,因此新的"狗条目减去gps" object仅在其中一个字段不同时返回。
  • 将GPS值存储在由ID键控的单独查找表中,而不是嵌套在狗条目本身内,这样对GPS阵列的更新不会导致新的狗条目引用。

答案 1 :(得分:0)

注意:这些评论中有详细信息

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