我目前正在研究React JS和React本机框架。在中途,当我读到Facebook的Flux和Redux实现时,我遇到了不变性或不变性JS库。
问题是,为什么不变性如此重要?变异对象有什么错?这不是让事情变得简单吗?
举个例子,让我们考虑一个简单的新闻阅读器应用程序,它的开头屏幕是新闻标题的列表视图。
如果我设置一个对象数组的值,我不能操作它。这就是不可变原则所说的,对吗?(如果我错了请纠正我。)但是,如果我有一个必须更新的新新闻对象呢?在通常情况下,我可以将对象添加到数组中。在这种情况下我如何实现?删除商店并重新创建?向数组中添加对象不是更便宜的操作吗?
问题是,为什么不变性如此重要?变异对象有什么错?这不是让事情变得简单吗?
事实上,恰恰相反:易变性使事情变得更加复杂,至少从长远来看是这样。是的,它使你最初的编码变得更容易,因为你可以随时随地改变事情,但是当你的程序变大时,它就变成了一个问题——
当你让一切都不可变时,这意味着数据不能再被突然改变了。您可以肯定地知道,如果您将一个值传递给一个函数,它就不能在该函数中更改。
简而言之:如果你使用不可变的值,就很容易对你的代码进行推理:每个人都会得到你的数据的唯一*副本,所以它不会破坏代码的其他部分。想象一下,这使在多线程环境中工作变得多么容易!
注1:不变性会带来潜在的性能损失,这取决于您所做的工作,但像Immutable.js这样的东西会尽可能地优化性能。
注2:在你不确定的不太可能的情况下,我mmutable.js和ES6const
的意思非常不同。
在通常情况下,我可以将对象添加到数组中。在这种情况下,我如何实现目标?删除存储
是的,你的新闻例子非常好,你的推理也完全正确:你不能仅仅修改你现有的列表,所以你需要创建一个新的列表:
var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);
TL/DR:在JavaScript中,不可变性更像是一种时尚趋势,而不是必需品。如果您正在使用React,它确实为状态管理中一些令人困惑的设计选择提供了一个简洁的解决方案。然而,在大多数其他情况下,它不会在引入的复杂性上增加足够的价值,更多的是为了填充简历,而不是满足客户的实际需求。
长篇回答:阅读下面。
为什么不可变性在JavaScript中如此重要(或需要)?
嗯,我很高兴你问了!
不久前,一个叫Dan Abramov的非常有才华的人写了一个名为Redux的javascript状态管理库,它使用纯函数和不可变性。他还制作了一些非常酷的视频,让这个想法非常容易理解(和销售)。
时间安排得很好。Angular的新颖性正在消退,JavaScript世界已经准备好专注于最新的具有适当酷度的东西,这个库不仅具有创新性,而且与React完美结合,React正被另一个硅谷巨头兜售。
尽管很悲伤,但时尚在JavaScript世界中占据主导地位。现在阿布拉莫夫被誉为半神,我们所有的凡人都必须服从永恒之道...不管有没有意义。
变异对象有什么错?
什么都没有!
事实上,程序员一直在为er修改对象。。。只要存在变异的对象。换句话说,50年的应用程序开发。
为什么要把事情复杂化?当您有对象cat
并且它死亡时,您真的需要第二个cat
来跟踪更改吗?大多数人只会说,cat.isDead=true
,然后就完蛋了。
(变异物体)不会让事情变得简单吗?
对当然了!
特别是在JavaScript中,它在实践中最有用,用于呈现在其他地方(如数据库)维护的某些状态的视图。
如果我有一个必须更新的新新闻对象怎么办。。。在这种情况下,我如何实现目标?删除存储
那么,您可以使用传统的方法来更新News
对象,这样您对该对象的内存表示就会改变(以及向用户显示的视图,或者人们希望如此)...
或者说...
您可以尝试性感的FP/不变性方法,并将对News
对象的更改添加到跟踪每个历史更改的数组中,这样您就可以遍历该数组,并找出正确的状态表示形式(呸!)。
我在努力学习这里的东西。请你开导我:)
时尚来来去去,伙计。给猫剥皮有很多种方法。
很抱歉,您不得不忍受不断变化的编程范例集带来的混乱。但是,嘿,欢迎来到俱乐部!!
现在,关于不变性,有几个要点需要记住,你会以只有天真才能聚集起来的狂热程度,把它们扔给你。
1) 不变性对于避免多线程环境中的竞争条件来说是非常棒的。
多线程环境(如C、Java和C#)在多个线程想要更改对象时,会犯锁定对象的错误。这对性能不利,但比数据损坏更好。然而,这还不如让一切都变得一成不变(主赞颂哈斯克尔!)。
但是,唉!在JavaScript中,您总是在单个线程上操作。甚至web工作人员(每个工作人员都在单独的上下文中运行)。因此,由于在执行上下文(所有这些可爱的全局变量和闭包)中不能有与线程相关的竞争条件,所以支持不变性的要点就不存在了。
(话虽如此,在web workers中使用纯函数有一个好处,那就是您不会期望在主线程上处理对象。)
2)不可变性可以(以某种方式)避免应用状态下的竞争条件。
这是问题的真正症结所在,大多数(React)开发人员会告诉您,不变性和FP可以以某种方式发挥这种魔力,使应用程序的状态变得可预测。
当然,这并不意味着你可以避免数据库中的竞争条件,要实现这一点,你必须协调所有浏览器中的所有用户,为此,你需要像WebSockets这样的后端推送技术(下面有更多信息)来广播变化每个运行应用程序的人。
这也不意味着在JavaScript中存在一些固有的问题,你的应用程序状态需要不可变性才能变得可预测,任何在React之前编码前端应用程序的开发人员都会告诉你这一点。
这个相当令人困惑的说法仅仅意味着,如果您使用React,您的应用程序很容易出现竞争条件,但不变性允许您消除这种痛苦。为什么?因为反应很特别。。它首先被设计为一个高度优化的呈现库,状态管理被颠覆,从而通过一个异步事件链(也称为“单向数据绑定”)管理组件状态,优化呈现,但您无法控制并依赖于您记住不要直接改变状态。。。
在这种情况下,很容易看出对不变性的需求与JavaScript的关系不大,而与React的关系很大:如果你的新应用程序中有大量相互依赖的更改,并且没有简单的方法来确定你当前的状态,你会感到困惑,因此,使用不变性来跟踪每一个历史变化是非常有意义的。
3)比赛条件非常糟糕。
如果你使用React,它们可能是。但是如果你选择了不同的框架,它们是罕见的。
此外,你通常有更大的问题要处理…像依赖地狱的问题。就像一个臃肿的代码库。比如你的CSS没有被加载。就像一个缓慢的构建过程,或者被困在一个单一的后端,这使得迭代几乎不可能。比如没有经验的开发人员不了解正在发生的事情,把事情搞得一团糟。
你知道的。现实但是,嘿,谁在乎这个?
4)不可变性使用引用类型来减少跟踪每个状态更改对性能的影响。
因为说真的,如果你想在每次你的状态改变时复制东西,你最好确保你对此很聪明。
5) 不变性允许你撤销东西。
因为呃...这是你的项目经理要求的第一个功能,对吗?
6)不可变状态与WebSocket结合有很多很酷的潜力
最后但并非最不重要的一点是,状态增量的累积与WebSocket的结合是一个非常引人注目的案例,它允许将状态作为一个不可变事件流进行轻松消费。。。
一旦一分钱落在这个概念上(状态是一个事件流——而不是代表最新观点的一组原始记录),不变的世界就变成了一个神奇的居住之地。这是一片充满奇迹和可能性的土地,超越了时间本身。如果做得好,这肯定会使实时应用程序更容易实现,你只需将事件流广播给所有感兴趣的人,这样他们就可以构建自己的当前表示,并将自己的更改写回公共流。
但在某个时刻,你醒来,意识到所有的奇迹和魔法并不是免费的。与你热切的同事不同,你的利益相关者(是的,付钱给你的人)很少关心哲学或时尚,而更关心他们为制造他们可以销售的产品而支付的钱。底线是,为不可变性编写代码更难,也更容易破坏它,另外,如果没有后端支持,那么拥有一个不可变的前端几乎没有什么意义。当(如果!)您最终说服您的利益相关者您应该通过WebSockets等推送技术发布和消费事件时,您就会发现在生产中进行扩展是多么痛苦。
现在,如果你选择接受一些建议。
选择使用FP/Immutability编写JavaScript,也可以使应用程序代码库更大、更复杂、更难管理。我强烈主张将这种方法限制在Redux还原程序中,除非您知道自己在做什么。。。如果您打算继续使用不可变性,那么将不可变状态应用于整个应用程序堆栈,而不仅仅是客户端,否则您将失去它的真正价值。
现在,如果你足够幸运,能够在工作中做出选择,那么试着运用你的智慧(或不运用智慧),由付钱给你的人做正确的事情。你可以根据你的经验、直觉或你周围发生的事情来做这件事(不可否认,如果每个人都在使用React/Redux,那么就有一个有效的理由,那就是更容易找到资源来继续你的工作)。。或者,您可以尝试恢复驱动的开发或炒作驱动的开发方法。它们可能更像是你喜欢的东西。
简言之,关于不变性的说法是,它会让你在你的同龄人中变得时髦,至少在下一个狂热到来之前,到那时你会很高兴继续前进。
现在,在自我治疗课程结束后,我想指出,我已经在我的博客中添加了这篇文章=
我最近一直在研究同一个话题。我会尽我所能回答你的问题,并尝试分享到目前为止我所学到的东西。
问题是,为什么不变性如此重要?变异对象有什么错?这不是让事情变得简单吗?
基本上,它归结为一个事实,即不变性增加了可预测性、性能(间接地),并允许进行突变跟踪。
可预测性
突变隐藏了变化,这会产生(意想不到的)副作用,这会导致令人讨厌的错误。当您强制执行不可变性时,您可以保持应用程序架构和心理模型的简单性,这使得对应用程序的推理更加容易。
表演
即使向不可变对象添加值意味着需要创建一个新实例,其中现有值需要复制,新值需要添加到新对象,这会消耗内存,不可变对象可以利用结构共享来减少内存开销...
所有更新都返回新值,但内部结构是共享的,以显著减少内存使用(和GC抖动)。这意味着,如果附加到包含1000个元素的向量,它实际上不会创建1001个元素长的新向量。最有可能的是,内部只分配了几个小对象。
你可以在这里读到更多。
突变跟踪
除了减少内存使用外,不变性还允许您通过使用引用和值相等来优化应用程序。这使我们很容易看到是否有任何变化。例如,react组件中的状态更改。您可以使用shouldComponentUpdate
通过比较状态对象来检查状态是否相同,并防止不必要的渲染。你可以在这里了解更多。
额外资源:
如果我先设置一个值的对象数组。我不能操纵它。这就是不可变原则所说的,对吗?如果我错了请纠正我)。但是,如果我有一个必须更新的新新闻对象呢?在通常情况下,我可以将对象添加到数组中。在这种情况下我如何实现?删除商店
是的,这是正确的。如果您对如何在应用程序中实现这一点感到困惑,我建议您看看redux是如何做到这一点的,以熟悉核心概念,这对我帮助很大。
我喜欢以Redux为例,因为它包含不变性。它有一个单一的不可变状态树(称为存储
),其中所有状态更改都是通过调度操作显式进行的,这些操作由一个reducer处理,reducer接受上一个状态和所述操作(一次一个)并返回应用程序的下一个状态。你可以在这里阅读更多关于它的核心原则。
在egghead.io上有一个很好的redux课程,redux的作者Dan Abramov将这些原则解释如下(为了更好地适应场景,我对代码进行了一些修改):
import React from 'react';
import ReactDOM from 'react-dom';
// Reducer.
const news = (state=[], action) => {
switch(action.type) {
case 'ADD_NEWS_ITEM': {
return [ ...state, action.newsItem ];
}
default: {
return state;
}
}
};
// Store.
const createStore = (reducer) => {
let state;
let listeners = [];
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(cb => cb !== listener);
};
};
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach( cb => cb() );
};
dispatch({});
return { subscribe, getState, dispatch };
};
// Initialize store with reducer.
const store = createStore(news);
// Component.
const News = React.createClass({
onAddNewsItem() {
const { newsTitle } = this.refs;
store.dispatch({
type: 'ADD_NEWS_ITEM',
newsItem: { title: newsTitle.value }
});
},
render() {
const { news } = this.props;
return (
<div>
<input ref="newsTitle" />
<button onClick={ this.onAddNewsItem }>add</button>
<ul>
{ news.map( ({ title }) => <li>{ title }</li>) }
</ul>
</div>
);
}
});
// Handler that will execute when the store dispatches.
const render = () => {
ReactDOM.render(
<News news={ store.getState() } />,
document.getElementById('news')
);
};
// Entry point.
store.subscribe(render);
render();
此外,这些视频还进一步详细演示了如何实现以下方面的不变性:
问题内容: 我目前正在研究React JS和React Native 框架。在阅读关于Facebook的Flux和Redux实现的文章时,我遇到了Immutability或Immutable- JS库 。 问题是,为什么不变性如此重要?更改对象有什么问题?它不是使事情变得简单吗? 举个例子,让我们考虑一个简单的 新闻阅读器 应用程序,其打开屏幕是新闻标题的列表视图。 如果我设置说 最初 具有值 的
问题内容: 试图弄清楚React的基础知识。 查看此页面上的第二个示例:https : //facebook.github.io/react/ 我看到tick()函数设置Timer类的状态,将前一个值加1。 但是,当我尝试实现自己的简单Counter类时,它失败了,并且出现控制台错误,提示 无法读取未定义的setState属性。 一些谷歌搜索显示我必须将其绑定到增量函数。但是,为什么在我看到的第一
问题内容: 在Java或Android中,有@Override注释。这是什么意思?我发现它是在方法来自子类或继承接口的方法时使用的,我想进一步了解,其他是@SuppressWarnings及其Anonation,如果是的话,java使用了多少注释以及用于什么目的。 问题答案: 这个问题在这里也很简洁地得到了回答: Android@Override用法 这是一个注释,可以用来告诉编译器和IDE,您打
编辑:哎呀,毕竟安装了铬。
问题内容: 示例1中的问题是“ this”引用了全局名称而不是myName对象。 我了解在将this的值设置为特定对象时使用bind()的方法,因此它可以解决示例1中的问题,但是为什么首先会出现此问题?这仅仅是创建Javascript的方式吗? 我还想知道为什么示例3解决了这个问题,以及示例2和示例3之间的区别。 问题答案: 为什么需要JavaScript bind()? 值是决定 如何 一个功能
问题内容: 我可以将数据发送到服务器,但是只有在使用FromBody-Attribute时才可以。 为什么无法使用Post从主体自动读取json数据? 后端Web API 前端angularjs 问题答案: 仅因为某事是POST请求,所以没有明确的规则如何传递参数。POST请求仍可以包含URL中编码的查询参数。方法参数应该是“简单”类型(字符串,整数等)的查询参数。 通常,复杂类型应该是POST表