什么是Immutable?
官网上是这么说的:
Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.
简单的说就是immutable是一种一旦创建就不能更改的数据类型,这样你在程序中就不用考虑每次对象的深浅拷贝,immutable有高级的数据更改检测机制。我们可以通过它提供的API进行数据的更改,且每次都是返回一个新的对象。
immutable.js每次进行数据操作都会返回一个新的对象,为了避免深度拷贝带来的性能损耗,immutable使用结构共享,就是说当其中一个节点发生改变之后,更改的只有这个节点本身和它的父节点们,其他节点数据则进行共享。
具体的操作API可以自行查看immutable.js官网https://immutable-js.github.io/immutable-js/
那immutable对于react应用的意义有什么呢??
React组件的重新渲染机制
1.当调用setState进行state的更改的时候
2.当父组件传递给子组件的props值改变的之后(更准确的说应该是父组件的重新渲染会带动子组件重新渲染,不管你props有没有变)
这里有个问题,其实第一条也不是完全准确的,因为当你调用setState的时候即便你state值没有改变也一样会触发re-render,请看代码:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
a: 1
}
}
render() {
console.log('我render了');
return (
<div className="App">
<button onClick={() => {
const oldState = this.state;
this.setState({
a: oldState.a
});
}}>click</button>
<span>{this.state.a}</span>
</div>
);
}
}
export default App;
我每一次的点击即便没有更改state,但是还是会触发render中的console方法!!(这个地方还有待研究),初步理解为调用setState无论更改state与否,都会触发re-render.
显然上面的渲染操作是我们不希望看到的,这是因为在react组件中shouldComponentUpdate总是返回true的.
要解决上述问题就需要在shouldComponentUpdate中进行两次state的比较,简单的数据结构比较是很容易的,如果数据结构层级很深,那样的话比较起来就会很麻烦。
Immutable解决此问题:
因为immutable是结构不可变的数据类型,每次操作更改之后都会返回一个新的对象,正要是可以大大减少我们diff的过程,只需要比较两个对象在内存中的引用是否相等就可以啦。
Immutable优点:
1.安全的操作state:
react不允许我们直接修改state,但是我们是可以通过setState来修改state。但是setState执行newState和oldState的merge过程是属于浅merge,并不会去深层的递归merge,那浅merge是什么意思呢?看下面代码:
class App extends Component {
constructor(props) {
super(props);
this.state = {
a: {
b: 2,
c: 3
}
}
}
render() {
console.log(this.state.a);
return (
<div className="App">
<button onClick={() => {
this.setState({
a: {
b: 2
}
});
}}>click</button>
</div>
);
}
}
可以看到这就是浅merge结果,第二次的设置state会把第一次的state覆盖掉,他定不会通过比较来决定那个字段需要覆盖掉。在实际的应用中,如果state不能deep merge的话,那么受到很大的限制,如果进行深度合并的话,一方面会带来效率上的问题,而且实现起来也比较复杂。同时这种浅merge,还有可能因为一个疏忽导致有用的字段被覆盖掉,造成程序的安全问题。
是引用immutable可以很有效的避免这个问题,因为每次操作数据返回的都已一个新对象,只需要用过immutable的set方法修改需要被改变的值,来返回一个新的state对象替换掉老的state对象,使我们操作state更加的安全。
2.节省内存
immutable.js使用了Structure Sharing,这可尽可能的复用内存,而且实现时间旅行功能也是很简单的。
let a = Immutable.Map({
select: 'user_a',
filter: Immutable.Map({ name: 'Cam' })
})
let b = a.set('select', 'user_b');
a === b; // false
a.get('filter') === b.get('filter'); // true
解释一下上代码的过程,首先创建了一个Map对象赋值给变量啊a, 然后a通过set方法修改select属性,新生成的Immutable对象赋值给b,虽然a,b是两个不同的对象,但是a和b共享了没有改变的filter节点,所以a.get('filter') 等于 b.get('filter')。
**Immutable为了节省性能和内存还会避免创建新的对象,如果数据没有改变的话它是不会创建新对象的: **
const originalMap = Immutable.Map({ b: 2 })
const updatedMap = Immutable.originalMap.set('b', 2)
updatedMap === originalMap // return ture
就是因为操作都会产生一个全新的Immutable对象,所以我认为可以将每次操作的产物存储在一个数组中,这个样就可以轻松拿到之前版本的数据,来完成时间旅行,版本回退的功能了。
3.拥抱函数式编程
Immutable本身就是函数式编程中的概念,即每次的输入相同输出的结果必然相同,个人认为这个编程里面要比面想对象的编程对应用更加的安全,也方便团队的合作和代码的调试。
Immutable的缺点:
1.增加学习成本
使用immutable和使用原生的js对象有很大的不同,比如在immutable中就不能够使用解构赋值,和对象运算符,取而代之的是使用immutable中get,set等api。但是immutable的常用API通过官网学习也是很简单掌握的。
2.侵入性强
当我们使用第三方组件库的时候将不得不将immutable通过toJS方法将其转为原生的JS对象。
3.容易与原生对象混淆
在实际开发过程中通常有些人会把immutable对象和原生JS对象用混,这个问题其实是可以通过命名规则的方法来解决,例如immutable对象我们可以给其加一个$前缀:
let $obj = Immutable.fromJS({a: 1}) // immutable对象
let obj = {a: 1} // 原生对象
但是还是建议在程序用如果引用了immutable就尽量不要使用原生的JS对象,除非特殊的情况不得已将immutable对象转为原生JS进行操作。因为两种对象互相转换其实还是挺耗费性能的,尽量避免这种转换。
总结:
当项目变得复杂时,每一次action对于生成的新state都会消耗一定的性能,所以为了防止不符合预期的re-render,减少不必要的render提高react的性能,可以通过immutable来最大实现符合我们预期的re-render。由于侵入性较强,新项目引入比较容易,老项目迁移需要评估迁移。对于一些提供给外部使用的公共组件,最好不要把 Immutable 对象直接暴露在对外接口中。
网友评论