Redux/ImmutableJS 嵌套状态 + 不可变集合的 Big-o 复杂性
Redux/ImmutableJS Nested States + Big-o Complexity of Immutable Collections
我正在努力为我的第一个 Redux (NGRX/Store) 项目解决不可变性问题。事实证明,避免状态突变非常痛苦,在与 Object.assign({}) 和状态突变错误作斗争之后,我发现了 Immutable.JS。这让事情变得容易多了。
假设我有一个金融交易应用程序,它需要在加载时在图表上显示一组柱状图。每秒几次,最后一个柱需要根据实时价格信息更新,并且每隔一段时间就会添加一个新柱。
对于 {1-n} 金融工具(EURUSD/GBPJPY/Gold/Oil 等),这一切都需要发生。所以我为我的应用程序的这一部分想出了这个模型:
export interface CandleState {
LastCompletedCandle : Candle;
InProgressCandle : Candle;
LastTick:Offer;
ClosedCandles:immutable.List<Candle>;
};
export interface AllCandleState {
instruments: immutable.Map<string, CandleState>
}
您会注意到我有一个包含不可变列表的不可变映射。所以我的第一个问题是:这样做 'immutability within immutability' 有什么意义吗?自致电
instruments.set("EURUSD", { [my new state] })
正在有效地返回一个全新的状态,因此我不清楚我是否需要像这样的嵌套不变性....我确实希望能够订阅 ClosedCandles 列表上的更改;让这个不可变的东西可以直接观察到变化吗?或者这些东西只是从 'top' 级别检测到的。
我想我的下一个问题是:我是否需要为此担心。我已经意识到更改不可变集合是一项非常昂贵的操作。如果我做 list.push 或 map.set 幕后实际发生的事情。我是在复制整个数组中的每个项目还是一个一个地映射到一个全新的 array/map - 每次我需要更改不可变集合中的某些内容时?还是我只是在更改参考或其他内容?
我希望有一些关于不可变集合的 Big-Oh Complexity 的已发布信息,这将使理解这些东西如何执行变得容易,但我无法在任何地方找到它。
Is there any point to doing 'immutability within immutability' like this?
对于大多数部分,非常原始的答案是:不。 - 出于更改检测的目的,对象是否不可变并不重要 - 但是它确实帮助您强制执行始终创建新对象的概念,并且永远不要改变 store 之外的现有状态(甚至在 reducer 本身)。
I do want to be able to subscribe to changes on the ClosedCandles
list; will making this immutable allow things to observe that for
changes directly? Or are these things only detected from the 'top'
level.
是也不是。直接观察变化可以通过设置一个直接选择 instruments.ClosedCandles
的流来实现——然而这不是不变性的特殊情况,这可以在有或没有不变性的情况下完成。
现在关于不变性和 自上而下 部分:情况总是如此,当你想改变某个对象的 n 层级时,必须重新创建更改后的对象(直到根目录),这仅仅是因为如果不可变则无法更改父对象,因此您不能只设置新的引用。
示例:
root <-- to enable changing the map1, you have to recreate the root, since it is immutable
|--map1 <-- to enable changing the set2, you have to recreate this map, since it is immutable
| |--set1 <-- untouched, the "new version" of map1 will reference the same "set1"
| \--set2 <-- to enable changing the attribute2, you have to recreate this set, since it is also immutable
| |--attribute1 <-- untouched, the "new version" of set2 will just have the same reference on this object as the "old version"
| \--attribute2 <-- you want to alter this attribute
|
|--map2 <-- untouched, the "new version" of root will reference the same "map2"
\--map3 <-- untouched, the "new version" of root will reference the same "map3"
I guess my next question is: do I need to worry about this at all.
I've got it into my head that changing an immutable collection is a
very expensive operation. If I do list.push or map.set what is
actually happening under the hood. Am I copying every single item in
the entire array or map into to a brand new array/map one by one -
every time I need to change something in an immutable collection? Or
am I just changing a reference or something?
这主要取决于您如何使用不可变对象,大多数库对此进行了优化并将这些东西包装起来,因此您不必担心。至于性能:在大多数情况下,这没什么大不了的,大多数突变只会改变状态的一小部分,状态的其余部分将只包含新的引用,通常不会创建新的对象。
然而,在性能关键的情况下,您可以使用一种策略,即仅将不可变用于开发和测试构建,以确保您的应用程序正常工作,而对于生产构建,您可以停用不可变性以优化最后一点性能.
我正在努力为我的第一个 Redux (NGRX/Store) 项目解决不可变性问题。事实证明,避免状态突变非常痛苦,在与 Object.assign({}) 和状态突变错误作斗争之后,我发现了 Immutable.JS。这让事情变得容易多了。
假设我有一个金融交易应用程序,它需要在加载时在图表上显示一组柱状图。每秒几次,最后一个柱需要根据实时价格信息更新,并且每隔一段时间就会添加一个新柱。
对于 {1-n} 金融工具(EURUSD/GBPJPY/Gold/Oil 等),这一切都需要发生。所以我为我的应用程序的这一部分想出了这个模型:
export interface CandleState {
LastCompletedCandle : Candle;
InProgressCandle : Candle;
LastTick:Offer;
ClosedCandles:immutable.List<Candle>;
};
export interface AllCandleState {
instruments: immutable.Map<string, CandleState>
}
您会注意到我有一个包含不可变列表的不可变映射。所以我的第一个问题是:这样做 'immutability within immutability' 有什么意义吗?自致电
instruments.set("EURUSD", { [my new state] })
正在有效地返回一个全新的状态,因此我不清楚我是否需要像这样的嵌套不变性....我确实希望能够订阅 ClosedCandles 列表上的更改;让这个不可变的东西可以直接观察到变化吗?或者这些东西只是从 'top' 级别检测到的。
我想我的下一个问题是:我是否需要为此担心。我已经意识到更改不可变集合是一项非常昂贵的操作。如果我做 list.push 或 map.set 幕后实际发生的事情。我是在复制整个数组中的每个项目还是一个一个地映射到一个全新的 array/map - 每次我需要更改不可变集合中的某些内容时?还是我只是在更改参考或其他内容?
我希望有一些关于不可变集合的 Big-Oh Complexity 的已发布信息,这将使理解这些东西如何执行变得容易,但我无法在任何地方找到它。
Is there any point to doing 'immutability within immutability' like this?
对于大多数部分,非常原始的答案是:不。 - 出于更改检测的目的,对象是否不可变并不重要 - 但是它确实帮助您强制执行始终创建新对象的概念,并且永远不要改变 store 之外的现有状态(甚至在 reducer 本身)。
I do want to be able to subscribe to changes on the ClosedCandles list; will making this immutable allow things to observe that for changes directly? Or are these things only detected from the 'top' level.
是也不是。直接观察变化可以通过设置一个直接选择 instruments.ClosedCandles
的流来实现——然而这不是不变性的特殊情况,这可以在有或没有不变性的情况下完成。
现在关于不变性和 自上而下 部分:情况总是如此,当你想改变某个对象的 n 层级时,必须重新创建更改后的对象(直到根目录),这仅仅是因为如果不可变则无法更改父对象,因此您不能只设置新的引用。
示例:
root <-- to enable changing the map1, you have to recreate the root, since it is immutable
|--map1 <-- to enable changing the set2, you have to recreate this map, since it is immutable
| |--set1 <-- untouched, the "new version" of map1 will reference the same "set1"
| \--set2 <-- to enable changing the attribute2, you have to recreate this set, since it is also immutable
| |--attribute1 <-- untouched, the "new version" of set2 will just have the same reference on this object as the "old version"
| \--attribute2 <-- you want to alter this attribute
|
|--map2 <-- untouched, the "new version" of root will reference the same "map2"
\--map3 <-- untouched, the "new version" of root will reference the same "map3"
I guess my next question is: do I need to worry about this at all. I've got it into my head that changing an immutable collection is a very expensive operation. If I do list.push or map.set what is actually happening under the hood. Am I copying every single item in the entire array or map into to a brand new array/map one by one - every time I need to change something in an immutable collection? Or am I just changing a reference or something?
这主要取决于您如何使用不可变对象,大多数库对此进行了优化并将这些东西包装起来,因此您不必担心。至于性能:在大多数情况下,这没什么大不了的,大多数突变只会改变状态的一小部分,状态的其余部分将只包含新的引用,通常不会创建新的对象。
然而,在性能关键的情况下,您可以使用一种策略,即仅将不可变用于开发和测试构建,以确保您的应用程序正常工作,而对于生产构建,您可以停用不可变性以优化最后一点性能.