ngrx 中的 reducer 和不变性

Posted

技术标签:

【中文标题】ngrx 中的 reducer 和不变性【英文标题】:Reducers and immutability in ngrx 【发布时间】:2019-01-13 19:13:17 【问题描述】:

在ngrx 中创建reducer 函数时,我读到的所有地方都说我应该返回原始/先前状态的副本。通过使用扩展运算符或使用库或 JSON.parse(JSON.stringify(state)) 之类的技巧。

但我在那里发现了一个问题,但我找不到任何人在谈论它。 reducer 中返回的最后一个状态是与所有当前订阅者和未来订阅者共享的状态。 这意味着使用某个存储的所有组件都将看到相同的状态对象。

这也意味着,如果状态中的任何值在一个组件中发生更改(没有调度操作),商店实际上会修改该值,但不会通知其他组件。 如果要在任何地方共享,那么返回当前状态的副本有什么意义?

不可变一词一直在使用,但这种状态根本不是不可变的,因为存储返回它自己的内部对象,而不是它的副本。

我了解不可变部分是否是开发人员需要遵循的概念。但是,原始对象/值的副本需要在使用它的组件中完成。从 reducer 返回浅拷贝或深拷贝似乎只是浪费处理能力和内存。

【问题讨论】:

您不应该改变组件的状态。为了确保这一点,您可以使用类似 ngrx-store-freeze 的东西,如果这样做会引发错误。 好的,但是让reducer返回副本有什么意义呢?如果组件不应该直接改变 store 的值,reducer 总是可以操作同一个 state 对象,对我来说增加开销和代码复杂度是没有意义的。 我很确定,如果您稍微挖掘一下,您的问题的答案已经得到了足够的时间回答。 “增加开销和代码复杂性对我来说没有意义”这可能是因为您现在不需要 Redux 模式。 通过代码复杂性和开销我并不是指 redux 本身,我只是在研究它。我的意思是让减速器返回副本,这对我来说没有意义,正如我在原始问题和我在此处的回复中所解释的那样。在我读到的所有地方,他们都说您应该返回副本以使其不可变,但这并不能使其不可变,这就是我的观点。 您创建一个“新”状态是为了创建对该状态的新引用。这样做的好处是可以记住您的选择器,并且您可以将更改检测策略更改为OnPush 【参考方案1】:

我会试着回答。

伪代码中的 reducer 如下所示:

myReducer(state, action) 

  switch(action) 

    case ACTION_1:
      return ...state, prop: action.payload

    case ACTION_2:
      const newState = _.cloneDeep(state)
      newState.prop = action.payload
      return newState

    default:
      return state
  

如果 ACTION_1 你没有改变状态。扩展运算符使用新引用创建一个新对象,而新引用是发出更改信号所需要的。

如果 ACTION_2 您正在克隆状态。你改变克隆状态并返回它。因为克隆状态有一个新的对象引用,所以它表明已经进行了更改并且每个人都很高兴。

在默认情况下,任何其他操作(例如 ACTION_3)都将被忽略,并返回原始状态,表示状态未更改。对象引用没有改变,因此发出“没有改变”的信号(这就是为什么不改变原始状态很重要的原因)。

当一个动作被触发时,该动作被传递给每个reducer。因此,不想修改其关联状态的 reducer 可以通过依赖默认 case 语句来忽略该操作。

单个操作可以并且经常会触发多个 reducer 中的状态更改。

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

PS 有一个很棒的库,叫做 ngrx-store-freeze,它将强制执行“无突变”原则。如果你改变状态,它会提前抛出错误。这有助于避免难以追踪的错误。使用元减速器连接到 store freeze。

PPS 使用对象引用来确定更改的全部目的是因为检查对象引用比检查对象上的每个值以查看它是否已更改要快得多。这就是不变性如此重要的原因。

【讨论】:

仅作记录:我测试过总是返回相同的状态对象并且订阅者确实会收到通知。 好的,那么我建议你使用更智能的ngrx选择器。 你熟悉选择器吗?如果没有,我会为您转储一些示例代码。 最大的问题是何时使用扩展运算符以及何时使用 cloneDeep。众所周知,展开运算符是浅拷贝。它将包括对原始状态的现有内部对象/数组的引用。因此,如果某些组件依赖于作为状态属性的数组/对象,它将不会被通知更改。有时这是需要的行为,有时不是...... 我是 Redux/ngrx 的新手。你说>>单个动作可以并且经常会触发多个reducer中的状态更改。 Action -> Reducers -> Views 链,如果两个reducer 都订阅相同的action 并且都返回状态的新副本,那么哪个“获胜”?还是他们再次合并?

以上是关于ngrx 中的 reducer 和不变性的主要内容,如果未能解决你的问题,请参考以下文章

NGRX - 如何通过reducer更新数组中对象中的字段?

ngRx 官方示例分析 - 3. reducers

在 @ngrx/store 4.0 中提供 root reducer

更新 ngrx/store 中的对象

如何清除#ngrx 中的状态?

如何将 ngrx-store-freeze 与 nx 工作区一起使用