我目前正在开发React JS和React Native框架。在半路上,当我读到Facebook的Flux和Redux实现时,我遇到了Immutability或Immutable-JS库。
问题是,为什么不变性如此重要?改变对象有什么错?这不是让事情变得简单了吗?
举个例子,让我们考虑一个简单的新闻阅读器应用程序,它的打开屏幕是一个新闻标题的列表视图。
如果我设置一个带有值的对象数组,一开始我不能操作它。这就是不可变原理,对吧?(如果我说错了请指正。)
但是,如果我有一个新的News对象需要更新怎么办?通常情况下,我可以将对象添加到数组中。
在这种情况下我该如何实现呢?删除存储并重新创建它?
向数组中添加对象难道不是一种成本较低的操作吗?
为什么不可变性在JavaScript中如此重要(或需要)?
不可变性可以在不同的上下文中跟踪,但最重要的是根据应用程序状态和应用程序UI跟踪它。
我认为JavaScript Redux模式是非常流行和现代的方法,因为你提到了。
对于UI,我们需要让它具有可预测性。
如果UI = f(应用程序状态),这将是可预测的。
应用程序(在JavaScript中)通过使用reducer函数实现的操作来改变状态。
reducer函数只是接受动作和旧状态,并返回新状态,保持旧状态不变。
new state = r(current state, action)
好处是:你可以穿越状态,因为所有的状态对象都被保存了,你可以在任何状态下呈现应用程序,因为UI = f(state)
所以你可以很容易地撤销/重做。
碰巧创建所有这些状态仍然可以是内存高效的,与Git类似是很好的,我们在Linux操作系统中有类似的符号链接(基于索引节点)。
举个例子:
const userMessage = {
user: "userId",
topic: "topicId"
content: {}
}
validateMessage(userMessage)
saveMessage(userMessage)
sendMessageViaEmail(userMessage)
**sendMessageViaMobilePush(userMessage)**
console.log(userMessage) // => ?
现在回答一些问题:
在可变代码中的sendMessageViaMobilePush(userMessage))下的userMessage是什么?
{
id: "xxx-xxx-xxx-xxx", //set by ..(Answer for question 3)
user:"John Tribe", //set by sendMessageViaEmail
topic: "Email title", //set by sendMessageViaEmail
status: FINAL, //set by saveMessage or could be set by sendMessageViaEmail
from: "..", //set by sendMessageViaEmail
to:"...", //set by sendMessageViaEmail
valid:true, //set by validateMessage
state: SENT //set by sendMessageViaEmail
}
Surprised?? Me too :d. But this is normal with mutability in javascript.
(in Java too but a bit in different way. When You expect null but get some object).
What is under userMessage on same line in immutable code?
const userMessage = {
user: "userId",
topic: "topicId",
content: {}
}
Easy right ?
Can You guess by which method "id" is updated in mutable code in Snippet 1 ??
By sendMessageViaEmail.
Why?
Why not?
Well it was at first updated by saveMessage,
but then overridden by sendMessageViaEmail.
In mutable code people didn't received push messages (sendMessageViaMobilePush). Can You guess why ??
because I am amazing developer :D and I put safety check in method sendMessageViaMobilePush(userMessage)
function sendMessageViaMobilePush(userMessage) {
if (userMessage.state != SENT) { //was set to SENT by sendMessageViaEmail
send(userMessage)
}
}
Even if You saw this method before,
was this possible for You to predict this behavior in mutable code ?
For me it wasn't.
希望这能帮助你理解在javascript中使用可变对象的主要问题。
请注意,当复杂性上升时,很难检查设置的内容和位置,特别是当您与他人一起工作时。
从前,线程之间的数据同步出现了一个问题。这个问题非常麻烦,有10多个解决方案。有些人试图从根本上解决这个问题。它是函数式编程诞生的地方。这就像马克思主义。我不明白Dan Abramov是怎么把这个想法卖给JS的,因为它是单线程的。他是个天才。
我可以举个小例子。在gcc中有一个属性__attribute__((pure))。编译器试图解决你的函数是否是纯的,如果你不特别声明它。你的函数可以是纯的,即使你的状态是可变的。不可变性只是保证你的函数是纯粹的100多种方法中的一种。实际上95%的函数都是纯函数。
如果你真的没有明确的理由,你就不应该使用任何限制(比如不可变性)。如果你想“撤销”某些状态,你可以创建事务。如果你想简化通信,你可以发送带有不可变数据的事件。这取决于你。
我写这条信息来自后马克思主义共和国。我相信任何思想的激进化都是错误的。
问题是,为什么不变性如此重要?改变对象有什么错?这不是让事情变得简单了吗?
事实上,情况恰恰相反:至少从长远来看,可变性会让事情变得更复杂。是的,它使你的初始编码更容易,因为你可以在任何你想要的地方修改东西,但当你的程序变大时,它就变成了一个问题——如果一个值改变了,是什么改变了它?
当你让所有东西都是不可变的,这意味着数据不能再被意外改变了。你肯定知道,如果你把一个值传递给一个函数,它就不能在那个函数中被改变。
简单地说:如果你使用不可变的值,它会让你的代码变得非常容易:每个人都有一个唯一的数据副本,所以它不会破坏它,破坏你代码的其他部分。想象一下,这使得在多线程环境中工作变得多么容易!
注1:不变性有潜在的性能成本,这取决于你在做什么,但像Immutable.js这样的东西会尽其所能优化。
注2:在你不确定的情况下,Immutable.js和ES6的const意味着非常不同的东西。
通常情况下,我可以将对象添加到数组中。在这种情况下我该如何实现呢?删除商店并重新创建它?向数组中添加对象难道不是一种成本较低的操作吗?PS:如果这个例子不是解释不变性的正确方式,请让我知道什么是正确的实际例子。
是的,你的新闻例子非常好,你的推理也非常正确:你不能只是修改现有的列表,所以你需要创建一个新的列表:
var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);
我已经为可变(或不可变)状态创建了一个框架不可知的开源(MIT)库,它可以取代所有那些不可变的存储,如库(redux, vuex等…)
对我来说,不可变状态是丑陋的,因为有太多的工作要做(很多简单的读/写操作),代码可读性较差,大数据集的性能不能接受(整个组件重新渲染:/)。
对于深度状态观察者,我只能用点表示法更新一个节点并使用通配符。我还可以创建状态的历史记录(撤销/重做/时间旅行),只保留那些已经改变的具体值{path:value} =较少的内存使用。
使用深度状态观测器,我可以对事情进行微调,并对组件行为进行粒度控制,因此性能可以大幅提高。代码可读性更强,重构也更容易——只需要搜索和替换路径字符串(不需要更改代码/逻辑)。