Immutable.js关系

use*_*183 26 javascript reactjs immutable.js

想象一下约翰有两个孩子爱丽丝和鲍勃的情况,鲍勃有一只猫猎户座.

var Immutable = require('immutable');

var parent = Immutable.Map({name: 'John'});
var childrens = Immutable.List([
    Immutable.Map({name: 'Alice', parent: parent}),
    Immutable.Map({name: 'Bob', parent: parent})
]);
var cat = Immutable.Map({name: 'Orion', owner: childrens.get(1)});
Run Code Online (Sandbox Code Playgroud)

几年后,约翰想要重命名为简.

var renamedParent = parent.set('name', 'Jane');
Run Code Online (Sandbox Code Playgroud)

......让孩子们了解它.

childrens = childrens.map(function(children) {
    children.set('parent', renamedParent);
});
Run Code Online (Sandbox Code Playgroud)

然后我必须更新cat因为Bob改变了.

cat = cat.set('owner', childrens.get(1));
Run Code Online (Sandbox Code Playgroud)

当一个对象发生变化时,是否可以自动更新所有相关对象?我看了Cursors,但我不确定它们是否是解决方案.如果有可能,你能举个例子吗?

rsp*_*rsp 54

解释这个问题:

当一个对象在不可变集合中更改时,是否可以自动更新所有相关对象?

简短的回答

没有.

答案很长

不,但在不可变数据结构中没有任何变化,所以这不是问题.

甚至更长的答案

它更复杂......

不变性

不可变对象的重点是,如果你有一个不可变对象的引用,你就不必费心去检查它的任何属性是否已经改变.那么,这不是问题吗?好...

后果

这有一些后果 - 无论好坏取决于您的期望:

  • pass-by-valuepass-by-reference语义之间没有区别
  • 一些比较可以更容易
  • 当您将引用传递给某个对象时,您不必担心代码的其他部分会更改它
  • 当您从某个地方获得对象的引用时,您知道它永远不会改变
  • 您可以避免一些并发问题,因为没有时间变化的概念
  • 当没有任何变化时,你不必担心变化是否是原子的
  • 使用不可变数据结构实现软件事务存储器(STM)更容易

但世界是可变的

当然,在实践中,我们经常处理随时间变化的价值观.似乎不可变状态不能描述可变世界,但人们可以通过某种方式来处理它.

以这种方式看待它:如果你在某个ID上有你的地址并且你移动到另一个地址,那么应该改变该ID以与新的真实数据保持一致,因为你不再住在那个地址.但是,如果您在购买包含地址的物品时收到发票,然后更改地址,则发票保持不变,因为在发票开具时您居住在该地址仍然是正确的.现实世界中的一些数据表示是不可变的,就像该示例中的发票一样,并且一些数据表示像ID一样可变.

现在举个例子,如果你选择使用不可变结构来建模数据,你必须考虑一下你从我的例子中考虑发票的方式.数据可能不是最新的,但在某些时间点它始终是一致的,并且永远不会改变.

如何应对变化

那么如何使用不可变数据建模​​变更呢?在Clojure中使用Vars,Refs,AtomsAgents解决了一个很好的方法,ClojureScript(针对JavaScript的Clojure编译器)支持其中一些(特别是Atoms应该像在Clojure中一样工作但是有no Refs,STM,Vars或Agents - 看看ClojureScript和Clojure在并发功能方面有什么区别.

看看Atom是如何在ClojureScript中实现的,似乎你可以使用普通的JavaScript对象来实现相同的目标.它适用于像JavaScript对象本身是可变的,但它有一个属性是对不可变对象的引用 - 你将无法更改该不可变对象的任何属性,但你将能够构造一个不同的不可变对象,并将旧的对象交换到顶级可变对象中的新对象.

Haskell这样纯粹功能性的其他语言可能有不同的方式来处理可变世界,比如monads(一个众所周知难以解释的概念--Javass的作者Douglas Crockford : JSON 的好部分发现者将其归因于"monadic curse"在他的谈话Monads和Gonads).

你的问题似乎很简单,但它触及的问题实际上非常复杂.当然,如果在一个对象发生变化时是否有可能自动更新所有相关对象,那么对于回答"否"是错误的点,但它比这更复杂,并且说在不可变对象中没有任何变化(所以这个问题永远不会发生)同样无益.

可能的解决方案

您可以拥有一个顶级对象或变量,您可以从中始终访问所有结构.假设你有:

var data = { value: Immutable.Map({...}) }
Run Code Online (Sandbox Code Playgroud)

如果您始终使用data.value(或使用更好的名称)访问您的数据,那么您可以将data代码传递给代码的其他部分,并且只要状态发生变化,您就可以将新的Immutable.Map({...})分配给您的data.value此时,您使用的所有代码都data将获得新值.

如何以及何时更新data.value到新的不可变结构可以通过从用于更新状态的setter函数自动触发来解决.

另一种方法是在每个结构的层次上使用类似的技巧,例如 - 我使用变量的原始拼写:

var parent = {data: Immutable.Map({name: 'John'}) };
var childrens = {data: Immutable.List([
    Immutable.Map({name: 'Alice', parent: parent}),
    Immutable.Map({name: 'Bob', parent: parent})
])};
Run Code Online (Sandbox Code Playgroud)

但是你必须要记住,你所拥有的值不是不可变结构,而是那些引入了不可变结构的附加对象,这些结构引入了额外的间接层.

一些阅读

我建议的是看一些项目和文章:

  • Peter Hausel撰写的不可改变的React文章
  • Morearty.js - 像在Om 中一样使用React中的不可变状态,但是用纯JavaScript编写
  • react-cursor - 用于Facebook的功能状态管理抽象
  • Omniscient - 一个为React组件提供抽象的库,允许快速自上而下的渲染包含不可变数据*  Om - ClojureScript接口到React
  • mori - 在JavaScript中使用ClojureScript的持久数据结构的库
  • Fluxy - Facebook的Flux 架构的实现
  • Facebook的Immutable - 持久的JavaScript数据集合,当与Facebook ReactFacebook Flux结合时,面临着类似的问题(搜索如何将Immutable与React和Flux结合起来可能会给你一些好主意)

我希望这个答案即使不给出简单的解决方案也会有所帮助,因为用不可变结构描述可变世界是一个非常有趣和重要的问题.

其他方法

对于不可变性的不同方法,使用代表数据的不可变对象(不一定是常量),请参阅Yegor Bugayenko及其文章中的Immutable Objects vs. Common Sense网络研讨会:

Yegor Bugayenko使用术语"不可变",其意义略微不同于它在函数式编程环境中的意义.他没有使用不可变或持久的数据结构和函数式编程,而是提倡在原始意义上使用面向对象编程,这种方式是你永远不会实际改变任何对象,但你可以要求它改变一些状态,或者改变一些状态.数据,本身被认为是与对象分开的.很容易想象一个与关系数据库对话的不可变对象.对象本身可以是不可变的,但它仍然可以更新存储在数据库中的数据.有点难以想象存储在RAM中的某些数据可以被认为与数据库中的数据完全独立,但实际上差别不大.如果您将对象视为暴露某些行为的自治实体并且您尊重它们的抽象边界,那么将对象视为与数据不同的东西实际上很有意义,然后您可以使用具有可变数据的不可变对象.

如果有任何需要澄清,请评论.