我目前正在开发React JS和React Native框架。在半路上,当我读到Facebook的Flux和Redux实现时,我遇到了Immutability或Immutable-JS库。
问题是,为什么不变性如此重要?改变对象有什么错?这不是让事情变得简单了吗?
举个例子,让我们考虑一个简单的新闻阅读器应用程序,它的打开屏幕是一个新闻标题的列表视图。
如果我设置一个带有值的对象数组,一开始我不能操作它。这就是不可变原理,对吧?(如果我说错了请指正。)
但是,如果我有一个新的News对象需要更新怎么办?通常情况下,我可以将对象添加到数组中。
在这种情况下我该如何实现呢?删除存储并重新创建它?
向数组中添加对象难道不是一种成本较低的操作吗?
我已经为可变(或不可变)状态创建了一个框架不可知的开源(MIT)库,它可以取代所有那些不可变的存储,如库(redux, vuex等…)
对我来说,不可变状态是丑陋的,因为有太多的工作要做(很多简单的读/写操作),代码可读性较差,大数据集的性能不能接受(整个组件重新渲染:/)。
对于深度状态观察者,我只能用点表示法更新一个节点并使用通配符。我还可以创建状态的历史记录(撤销/重做/时间旅行),只保留那些已经改变的具体值{path:value} =较少的内存使用。
使用深度状态观测器,我可以对事情进行微调,并对组件行为进行粒度控制,因此性能可以大幅提高。代码可读性更强,重构也更容易——只需要搜索和替换路径字符串(不需要更改代码/逻辑)。
我已经为可变(或不可变)状态创建了一个框架不可知的开源(MIT)库,它可以取代所有那些不可变的存储,如库(redux, vuex等…)
对我来说,不可变状态是丑陋的,因为有太多的工作要做(很多简单的读/写操作),代码可读性较差,大数据集的性能不能接受(整个组件重新渲染:/)。
对于深度状态观察者,我只能用点表示法更新一个节点并使用通配符。我还可以创建状态的历史记录(撤销/重做/时间旅行),只保留那些已经改变的具体值{path:value} =较少的内存使用。
使用深度状态观测器,我可以对事情进行微调,并对组件行为进行粒度控制,因此性能可以大幅提高。代码可读性更强,重构也更容易——只需要搜索和替换路径字符串(不需要更改代码/逻辑)。
问题是,为什么不变性如此重要?改变对象有什么错?这不是让事情变得简单了吗?
关于可变性
从技术角度来看,可变性并没有错。它是快速的,它是重复使用内存。开发人员从一开始就习惯了它(我记得)。可变性的使用存在问题,也会带来一些麻烦。
如果object不与任何东西共享,例如存在于函数的作用域中并且不对外公开,那么就很难看到不变性的好处。在这种情况下,不可变是没有意义的。一成不变的感觉始于某些东西被分享。
可变性头痛
可变的共享结构很容易产生许多陷阱。对访问引用的代码的任何部分的任何更改都会影响到具有此引用可见性的其他部分。这种影响将所有部分连接在一起,即使它们不应该意识到不同的模块。一个函数的突变可能会导致应用程序的完全不同部分崩溃。这样的事情是一个糟糕的副作用。
其次经常与突变的问题是损坏的状态。当突变过程中途失败,一些字段被修改,一些字段没有被修改时,就会发生损坏状态。
更重要的是,通过突变很难追踪变化。简单的参考检查不会显示出差异,要知道发生了什么变化需要做一些深入的检查。此外,为了监测变化,还需要引入一些可观察的模式。
最后,突变是信任缺失的原因。如果某个结构可以变异,你怎么能确定它有你想要的价值。
const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }
如上例所示,传递可变结构总是可以通过具有不同的结构来完成。函数doSomething正在改变外部给定的属性。没有对代码的信任,你真的不知道你拥有什么,你将拥有什么。所有这些问题的发生是因为:可变结构表示指向内存的指针。
不可变性与价值有关
不可变性意味着改变不是在相同的对象、结构上完成的,而是在新的对象、结构中表示的。这是因为引用不仅表示内存指针的值。每一次改变都会创造新的价值,而不会改变旧的价值。这样清晰的规则给予了信任和代码的可预测性。函数使用起来是安全的,因为它们处理的不是突变,而是具有自己值的自己的版本。
使用值而不是内存容器可以确定每个对象都表示特定的不可更改的值,并且使用它是安全的。
不可变结构表示值。
我将在一篇中型文章(https://medium.com/@macsikora/the-state-of- immutabil-169d2cd11310)中更深入地探讨这个主题
为什么不可变性在JavaScript中如此重要(或需要)?
不可变性可以在不同的上下文中跟踪,但最重要的是根据应用程序状态和应用程序UI跟踪它。
我认为JavaScript Redux模式是非常流行和现代的方法,因为你提到了。
对于UI,我们需要让它具有可预测性。
如果UI = f(应用程序状态),这将是可预测的。
应用程序(在JavaScript中)通过使用reducer函数实现的操作来改变状态。
reducer函数只是接受动作和旧状态,并返回新状态,保持旧状态不变。
new state = r(current state, action)
好处是:你可以穿越状态,因为所有的状态对象都被保存了,你可以在任何状态下呈现应用程序,因为UI = f(state)
所以你可以很容易地撤销/重做。
碰巧创建所有这些状态仍然可以是内存高效的,与Git类似是很好的,我们在Linux操作系统中有类似的符号链接(基于索引节点)。
问题是,为什么不变性如此重要?改变对象有什么错?这不是让事情变得简单了吗?
事实上,情况恰恰相反:至少从长远来看,可变性会让事情变得更复杂。是的,它使你的初始编码更容易,因为你可以在任何你想要的地方修改东西,但当你的程序变大时,它就变成了一个问题——如果一个值改变了,是什么改变了它?
当你让所有东西都是不可变的,这意味着数据不能再被意外改变了。你肯定知道,如果你把一个值传递给一个函数,它就不能在那个函数中被改变。
简单地说:如果你使用不可变的值,它会让你的代码变得非常容易:每个人都有一个唯一的数据副本,所以它不会破坏它,破坏你代码的其他部分。想象一下,这使得在多线程环境中工作变得多么容易!
注1:不变性有潜在的性能成本,这取决于你在做什么,但像Immutable.js这样的东西会尽其所能优化。
注2:在你不确定的情况下,Immutable.js和ES6的const意味着非常不同的东西。
通常情况下,我可以将对象添加到数组中。在这种情况下我该如何实现呢?删除商店并重新创建它?向数组中添加对象难道不是一种成本较低的操作吗?PS:如果这个例子不是解释不变性的正确方式,请让我知道什么是正确的实际例子。
是的,你的新闻例子非常好,你的推理也非常正确:你不能只是修改现有的列表,所以你需要创建一个新的列表:
var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);