深拷贝或不深拷贝 - 无论如何,为什么 ngrx 的状态应该是不可变的?

Chr*_*ian 9 state immutability redux ngrx angular

我是 ngrx 的新手(并且从未使用过 redux)并且我试图理解这一切 - 特别是是否需要状态的深层副本。这是我到目前为止所学到的以及仍然让我感到困惑的内容(以粗体显示)。

该NGRX文档状态

每个 reducer 函数都会获取最新调度的 Action、当前状态,并确定是返回新修改的状态还是原始状态。

他们还指出状态转换需要不变地发生——如果你改变状态,它需要在一个副本上:

每个动作都不变地处理状态转换。这意味着状态转换不会修改原始状态,而是使用扩展运算符返回一个新的状态对象。

但是,他们没有说明为什么这是必要的,除了促进引用完整性(我不太确定如何确保您的引用指向数据 - 我只知道关系数据库上下文中的术语 - 全部发挥作用):

spread 语法将属性从当前状态复制到对象中,创建一个新的引用。这确保了每次更改都会产生一个新状态,从而保持更改的纯度。这也促进了引用完整性,确保在发生状态更改时丢弃旧引用。

不过,那(参照完整性和上面的部分)并没有真正解释为什么这是必要的。
在 SO 的其他地方,我发现一条评论表明它允许将 Angular 的更改检测策略更改为OnPush.

(我也有点困惑,如果一个动作可以触发多个 reducer,结果状态副本可以合并,但这显然是由每个 reducer 专门处理单独的状态切片和 redux 意识到这一点来解释的。)

不过,重要的事情似乎是,状态的浅或深副本创建了一个新的 reference,这意味着 ngrx 正在向其订阅者推送更改:

如果返回的对象引用发生了变化,它将触发有关特定状态的任何相关 RxJS 状态订阅。使用一些好的 ngrx 选择器可以最小化触发哪些订阅。

一般来说,Redux FAQ列出了为什么不变性是一件好事以及为什么 redux 需要它
提高性能、更简单的调试、更容易推理、更安全的数据处理、浅层相等性检查

他们还说它

使复杂的变化检测技术能够简单且廉价地实施,确保更新 DOM 的计算成本高昂的过程仅在绝对必须时发生

正如刚刚指出的(作为不变性的要求之一)redux 会进行浅层相等性检查

然而,Nrgx 的文档推荐深度克隆(另外,如果副本引用旧对象,状态并不是真正不可变的,我想):

注意:spread 操作符只做浅拷贝,不处理深度嵌套的对象。您需要复制对象中的每个级别以确保不变性。有处理深度复制的库,包括 lodash 和 immer。

然而,深拷贝可能有“讨厌”的副作用(比如在 Angular 组件中使用克隆项时):

这个问题甚至延伸到了 Angular 的ngFor变化检测的工作方式(并且使用一个trackBy函数会使这进一步复杂化!):当我克隆每个项目Thing[]并让我的 reducer 返回一个新的克隆列表时Thing,Angular 会认为它是一个全新的列表(它在技术上是)并为列表中的所有项目运行更改检测:它们也将是全新的,因此,旧列表项目被删除,新项目被添加到 DOM。

假设你有一个ThingComponent为每个ThingngFor列表中。在那个组件中,ngOnChanges会触发。但事情是这样的: SimpleChanges传递给ngOnChanges将永远不会包含previousValues,因为整个列表被替换了,所以有以前的价值:从 Angulars 的角度来看,一切都是全新的。

作者还指出了一个解决方案 ( trackBy),但我现在想知道:
在 ngrx 中使用深拷贝真的是一个好主意(如果使库工作所需的只是一个新的对象引用,你真的需要深拷贝吗?对于根/状态对象)?最后一个引用听起来有点像只交换根对象、状态、out 以获得一个新的引用,然后触发订阅,但保留嵌套对象 - 尤其是列表 - 一个更好的主意。

sat*_*ime 3

  1. 深拷贝

我们的目标是让我们的应用程序尽可能快,这意味着在不需要或冗余时减少计算。

Angular 有两种更改检测策略 onPush 和 Default,第一个检查传递到输入的变量指针,第二个进行深度检查,并且对重对象非常重。但它们有一个特点——它们仅在数据发生更改时才重新渲染。

深度复制不好,因为它会导致在新对象指针下呈现相同的数据,这会导致渲染周期,并且因为数据相同,重新渲染的结果也将是相同的,除非它是时间相关的应用程序。


  1. 不可变状态

数据流动的最佳方式是从上到下,底层可以通知顶层更改数据。

在这种情况下,我们总是可以通过检查父母以及他们如何处理数据来找到数据是如何来到这里的,并且我们有一个数据正在发生变化的单点,我们可以在那里找到导致变化的人。

如果我们在需要更改的地方更改数据而不是通知高层,那么随着时间的推移,我们将无法轻松找到是谁做的,因为这些地方将在代码中无处不在,我们需要检查所有这些地方才能找到问题。