为什么 JavaScript 中的不变性如此重要(或需要)?
我目前正在研究 React JS 和 React Native 框架.在中途我遇到了 Immutability 或 Immutable-JS 库,当时我正在阅读关于Facebook 的 Flux 和 Redux 实现.
问题是,为什么不变性如此重要?改变对象有什么问题?这不是让事情变得简单吗?
举个例子,让我们考虑一个简单的新闻阅读器应用,其起始屏幕是新闻标题的列表视图.
如果我设置一个 对象数组 的值最初,我无法操作它.这就是不变性原理所说的,对吧?(如果我错了,请纠正我.)但是,如果我有一个必须更新的新 News 对象怎么办?在通常情况下,我可以将对象添加到数组中.在这种情况下我该如何实现?删除商店并重新创建它?向数组中添加对象不是成本更低的操作吗?
解决方案我最近一直在研究同一个主题.我会尽力回答您的问题并尝试分享我到目前为止所学到的知识.
<块引用>问题是,为什么不变性如此重要?有什么问题变异对象?这不是让事情变得简单吗?
基本上归结为不变性提高了可预测性、性能(间接)并允许进行突变跟踪这一事实.
可预测性
突变隐藏了变化,这会产生(意想不到的)副作用,这可能会导致令人讨厌的错误.当您强制执行不变性时,您可以使您的应用程序架构和心智模型保持简单,从而更容易推理您的应用程序.
性能
尽管向不可变对象添加值意味着需要在需要复制现有值的地方创建一个新实例,并且需要将新值添加到新对象中,这会消耗内存,但不可变对象可以利用结构共享以减少内存开销.
<块引用>所有更新都返回新值,但内部结构共享给大大减少内存使用(和 GC 抖动).这意味着如果您附加到具有 1000 个元素的向量,它实际上并没有创建一个 1001 个元素长的新向量.很可能,内部只有少数分配小对象.
您可以在此处了解更多信息.
突变跟踪
除了减少内存使用量之外,不变性还允许您通过使用引用和值相等来优化您的应用程序.这使得很容易查看是否有任何变化.例如,反应组件中的状态变化.您可以使用 shouldComponentUpdate
通过比较状态对象来检查状态是否相同,并防止不必要的渲染.您可以在这里了解更多信息.
其他资源:
- 不变性之道
- 不可变数据结构和 JavaScript
- JavaScript 中的不变性
如果我设置一个初始值的对象数组.我不能操纵它.这就是不变性原理所说的,对吧?(正确如果我错了我).但是,如果我有一个新的 News 对象必须被更新?在通常情况下,我可以将对象添加到大批.在这种情况下我该如何实现?删除商店 &重新创建它?向数组中添加对象不是成本更低的操作吗?
是的,这是正确的.如果您对如何在应用程序中实现这一点感到困惑,我建议您查看 redux 的作用这让我熟悉了核心概念,对我帮助很大.
我喜欢使用 Redux 作为示例,因为它包含不变性.它有一个单一的不可变状态树(称为 store
),其中所有状态更改都是显式的,通过调度由reducer 处理的动作,该reducer 接受先前的状态以及所述动作(一次一个)并返回应用程序的下一个状态.你可以阅读更多关于它的核心原则这里.
egghead.io 上有一个很棒的 redux 课程,其中 Dan Abramov,redux 的作者,对这些原则的解释如下(我稍微修改了代码以更好地适应场景):
从'react'导入反应;从 'react-dom' 导入 ReactDOM;//减速器.const news = (state=[], action) =>{开关(动作.类型){案例'ADD_NEWS_ITEM':{返回 [ ...state, action.newsItem ];}默认: {返回状态;}}};//店铺.const createStore = (reducer) =>{让状态;让听众 = [];const subscribe = (listener) =>{listeners.push(listener);返回()=>{listeners = listeners.filter(cb => cb !== listener);};};常量 getState = () =>状态;const dispatch = (action) =>{状态=减速器(状态,动作);listeners.forEach(cb => cb());};派遣({});返回{订阅,getState,调度};};//使用 reducer 初始化 store.常量商店 = 创建商店(新闻);//零件.常量新闻 = React.createClass({onAddNewsItem() {常量 { 新闻标题 } = this.refs;store.dispatch({类型:'ADD_NEWS_ITEM',newsItem:{标题:newsTitle.value}});},使成为() {常量 { 新闻 } = this.props;返回 (<输入 ref="newsTitle"/><button onClick={ this.onAddNewsItem }>添加</button><ul>{ news.map( ({ title }) => <li>{ title }</li>) }</ul></div>);}});//将在商店调度时执行的处理程序.常量渲染 = () =>{ReactDOM.render(<新闻news={ store.getState() }/>,document.getElementById('news'));};//入口点.store.subscribe(render);使成为();
此外,这些视频更详细地演示了如何实现以下方面的不变性:
- 数组
- 对象
I am currently working on React JS and React Native frameworks. On the half way road I came across Immutability or the Immutable-JS library, when I was reading about Facebook's Flux and Redux implementation.
The question is, why is immutability so important? What is wrong in mutating objects? Doesn't it make things simple?
Giving an example, let us consider a simple News reader app with the opening screen being a list view of news headlines.
If I set say an array of objects with a value initially I can't manipulate it. That's what immutability principle says, right? (Correct me if I am wrong.) But, what if I have a new News object that has to be updated? In usual case, I could have just added the object to the array. How do I achieve in this case? Delete the store and recreate it? Isn't adding an object to the array a less expensive operation?
解决方案I have recently been researching the same topic. I'll do my best to answer your question(s) and try to share what I have learned so far.
The question is, why is immutability so important? What is wrong in mutating objects? Doesn't it make things simple?
Basically it comes down to the fact that immutability increases predictability, performance (indirectly) and allows for mutation tracking.
Predictability
Mutation hides change, which create (unexpected) side effects, which can cause nasty bugs. When you enforce immutability you can keep your application architecture and mental model simple, which makes it easier to reason about your application.
Performance
Even though adding values to an immutable Object means that a new instance needs to be created where existing values need to be copied and new values need to be added to the new Object which cost memory, immutable Objects can make use of structural sharing to reduce memory overhead.
All updates return new values, but internally structures are shared to drastically reduce memory usage (and GC thrashing). This means that if you append to a vector with 1000 elements, it does not actually create a new vector 1001-elements long. Most likely, internally only a few small objects are allocated.
You can read more about this here.
Mutation Tracking
Besides reduced memory usage, immutability allows you to optimize your application by making use of reference- and value equality. This makes it really easy to see if anything has changed. For example a state change in a react component. You can use shouldComponentUpdate
to check if the state is identical by comparing state Objects and prevent unnecessary rendering.
You can read more about this here.
Additional resources:
- The Dao of Immutability
- Immutable Data Structures and JavaScript
- Immutability in JavaScript
If I set say an array of objects with a value initially. I can't manipulate it. That's what immutability principle says, right?(Correct me if I am wrong). But, what if I have a new News object that has to be updated? In usual case, I could have just added the object to the array. How do I achieve in this case? Delete the store & recreate it? Isn't adding an object to the array a less expensive operation?
Yes this is correct. If you're confused on how to implement this in your application I would recommend you to look at how redux does this to get familiar with the core concepts, it helped me a lot.
I like to use Redux as an example because it embraces immutability. It has a single immutable state tree (referred to as store
) where all state changes are explicit by dispatching actions which are processed by a reducer that accepts the previous state together with said actions (one at a time) and returns the next state of your application. You can read more about it's core principles here.
There is an excellent redux course on egghead.io where Dan Abramov, the author of redux, explains these principles as follows (I modified the code a bit to better fit the scenario):
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();
Also, these videos demonstrate in further detail how to achieve immutability for:
- Arrays
- Objects
相关文章