大话immutable.js
为啥要用immutable.js呢。毫不夸张的说。有了immutable.js(当然也有其他实现库)。。才能将react的性能发挥到极致!要是各位看官用过一段时间的react,而没有用immutable那么本文非常适合你。
1,对于react的来说,如果父组建有多个子组建
想象一下这种场景,一个父组建下面一大堆子组建。然后呢,这个父组建re-render。是不是下面的子组建都得跟着re-render。可是很多子组建里面是冤枉的啊!!很多子组建的props 和 state 然而并没有改变啊!!虽然virtual dom 的diff 算法很快。。但是性能也不是这么浪费的啊!!
以下是父组件代码。。负责输入name 和 age 然后循环显示name 和 age
export default class extends Component{
constructor(props){
super(props);
this.state = {
name:"",
age :"",
persons:[]
}
}
render(){
const {name,age,persons} = this.state
return (
<div>
<span>姓名:<input value={name} name="name" onChange={this._handleChange.bind(this)}></input>
<span>年龄:</span><input value={age} name="age" onChange={this._handleChange.bind(this)}></input>
<input type="button" onClick={this._handleClick.bind(this)} value="确认"></input>
{persons.map((person,index)=>(
<Person key={index} name={person.name} age={person.age}></Person>
))}
</div>
)
}
_handleChange(event){
this.setState({[event.target.name]:event.target.value})
}
_handleClick(){
const {name,age} = this.state
this.setState({
name:"",
age :"",
persons:this.state.persons.concat([{name:name,age:age}])
})
}
}
以下是子组建代码单纯的显示name和age而已
class Person extends Component {
componentWillReceiveProps(newProps){
console.log(`我新的props的name是${newProps.name},age是${newProps.age}。我以前的props的name是${this.props.name},age是${this.props.age}是我要re-render了`);
}
render() {
const {name,age} = this.props;
return (
<div>
<span>姓名:</span>
<span>{name}</span>
<span> age:</span>
<span>{age}</span>
</div>
)
}
}
这样看得出来了吧 每次添加人的时候就会导致子组件re-render了
2,PureRenderMixin
因为咱用的是es2015的 Component,所以已经不支持mixin了。。所以在这里我们用[Pure render decorator][5]代替PureRenderMixin,那么代码如下
import pureRender from "pure-render-decorator"
... @pureRender
class Person extends Component {
render() {
console.log("我re-render了");
const {name,age} = this.props; return (
<div>
<span>姓名:</span>
<span>{name}</span>
<span> age:</span>
<span>{age}</span>
</div>
)
}
}
果然可以做到pure render。。在必须render 的时候才render
是es7的Decorators语法。上面这么写就和下面这么写一样
class PersonOrigin extends Component {
render() {
console.log("我re-render了");
const {name,age} = this.props;
return (
<div>
<span>姓名:</span>
<span>{name}</span>
<span> age:</span>
<span>{age}</span>
</div>
)
}
}
const Person = pureRender(PersonOrigin)
pureRender其实就是一个函数,接受一个Component。把这个Component搞一搞,返回一个Component
看他pureRender的源代码就一目了然
function shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
function pureRende(component) {
component.prototype.shouldComponentUpdate = shouldComponentUpdate;
return component;
}
module.exports = pureRender;
pureRender很简单,就是把传进来的component的shouldComponentUpdate给重写掉了,原来的shouldComponentUpdate,无论怎样都是return ture,现在不了,我要用shallowCompare比一比,shallowCompare代码及其简单,如下
function shallowCompare(instance, nextProps, nextState) {
return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}
一目了然。分别拿现在props&state和要传进来的props&state,用shallowEqual比一比,要是props&state都一样的话,就return false,是不是感觉很完美?不。。这才刚刚开始,问题就出在shallowEqual上了
很多时候,父组件向子组件传props的时候,可能会传一个复杂类型,比如我们改下。
render() {
const {name,age,persons} = this.state
return (
<div>
...省略.....
{persons.map((person,index)=>(
<Person key={index} detail={person}></Person>
))}
</div>
)
}
person是一个复杂类型。。这就埋下了隐患,,在演示隐患前,我们先说说shallowEqual,是个什么东西,shallowEqual其实只比较props的第一层子属性是不是相同,就像上述代码,props 是如下
{
detail:{
name:"123",
age:"123"}
}
他只会比较props.detail ===nextProps.detail
那么问题来了,上代码
如果我想修改detail的时候考虑两种情况
情况一,我修改detail的内容,而不改detail的引用
这样就会引起一个bug,比如我修改detail.name,因为detail的引用没有改,所以
props.detail ===nextProps.detail 还是为true。。
所以我们为了安全起见必须修改detail的引用,(redux的reducer就是这么做的)
情况二,我修改detail的引用
这种虽然没有bug,但是容易误杀,比如如果我新旧两个detail的内容是一样的,岂不是还要,render。。所以还是不完美,,你可能会说用 深比较就好了,,但是 深比较及其消耗性能,要用递归保证每个子元素一样.
有人说 Immutable 可以给 React 应用带来数十倍的提升,也有人说 Immutable 的引入是近期 JavaScript 中伟大的发明,因为同期 React 太火,它的光芒被掩盖了。这些至少说明 Immutable 是很有价值的,下面我们来一探究竟。
JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。如 foo={a: 1}; bar=foo; bar.a=2 你会发现此时 foo.a 也被改成了 2。虽然这样做可以节约内存,但当应用复杂后,这就造成了非常大的隐患,Mutable 带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。
什么是 Immutable Data
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。请看下面动画:

// 原来的写法
let foo = {a: {b: 1}};
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b); // 打印 2
console.log(foo === bar); // 打印 true // 使用 immutable.js 后
import Immutable from 'immutable';
foo = Immutable.fromJS({a: {b: 1}});
bar = foo.setIn(['a', 'b'], 2); // 使用 setIn 赋值
console.log(foo.getIn(['a', 'b'])); // 使用 getIn 取值,打印 1
console.log(foo === bar); // 打印 false // 使用 seamless-immutable.js 后
import SImmutable from 'seamless-immutable';
foo = SImmutable({a: {b: 1}})
bar = foo.merge({a: { b: 2}}) // 使用 merge 赋值
console.log(foo.a.b); // 像原生 Object 一样取值,打印 1
console.log(foo === bar); // 打印 false
function touchAndLog(touchFn) {
let data = { key: 'value' };
touchFn(data);
console.log(data.key); // 猜猜会打印什么?
}
在不查看 touchFn 的代码的情况下,因为不确定它对 data 做了什么,你是不可能知道会打印什么(这不是废话吗)。但如果 data 是 Immutable 的呢,你可以很肯定的知道打印的是 value。
import { Map} from 'immutable';
let a = Map({
select: 'users',
filter: Map({ name: 'Cam' })
})
let b = a.set('select', 'people');
a === b; // false
a.get('filter') === b.get('filter'); // true
上面 a 和 b 共享了没有变化的 filter 节点。
Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 map.get('key') 而不是 map.key,array.get(0) 而不是 array[0]。另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。
两个 immutable 对象可以使用 === 来比较,这样是直接比较内存地址,性能最好。但即使两个对象的值是一样的,也会返回 false:
let map1 = Immutable.Map({a:1, b:1, c:1});
let map2 = Immutable.Map({a:1, b:1, c:1});
map1 === map2; // false
Immutable.is(map1, map2); // true
Immutable.is 比较的是两个对象的 hashCode 或 valueOf(对于 JavaScript 对象)。由于 immutable 内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。
后面会使用 Immutable.is 来减少 React 重复渲染,提高性能。
由于 Immutable 数据一般嵌套非常深,为了便于访问深层数据,Cursor 提供了可以直接访问这个深层数据的引用。
import Immutable from 'immutable';
import Cursor from 'immutable/contrib/cursor'; let data = Immutable.fromJS({ a: { b: { c: 1 } } });
// 让 cursor 指向 { c: 1 }
let cursor = Cursor.from(data, ['a', 'b'], newData => {
// 当 cursor 或其子 cursor 执行 update 时调用
console.log(newData);
}); cursor.get('c'); //
cursor = cursor.update('c', x => x + 1);
cursor.get('c'); //
setState 的一个技巧
React 建议把 this.state 当作 Immutable 的,因此修改前需要做一个 deepCopy,显得麻烦:
import '_' from 'lodash';
const Component = React.createClass({
getInitialState() {
return {
data: { times: 0 }
}
},
handleAdd() {
let data = _.cloneDeep(this.state.data);
data.times = data.times + 1;
this.setState({ data: data });
// 如果上面不做 cloneDeep,下面打印的结果会是已经加 1 后的值。
console.log(this.state.data.times);
}
}
使用 Immutable 后:
getInitialState() {
return {
data: Map({ times: 0 })
}
},
handleAdd() {
this.setState({ data: this.state.data.update('times', v => v + 1) });
// 这时的 times 并不会改变
console.log(this.state.data.get('times'));
}
上面的 handleAdd 可以简写成:
handleAdd() {
this.setState(({data}) => ({
data: data.update('times', v => v + 1) })
});
}
与 Flux 搭配使用
由于 Flux 并没有限定 Store 中数据的类型,使用 Immutable 非常简单。
现在是实现一个类似带有添加和撤销功能的 Store:
import { Map, OrderedMap } from 'immutable';
let todos = OrderedMap();
let history = []; // 普通数组,存放每次操作后产生的数据
let TodoStore = createStore({
getAll() { return todos; }
});
Dispatcher.register(action => {
if (action.actionType === 'create') {
let id = createGUID();
history.push(todos); // 记录当前操作前的数据,便于撤销
todos = todos.set(id, Map({
id: id,
complete: false,
text: action.text.trim()
}));
TodoStore.emitChange();
} else if (action.actionType === 'undo') {
// 这里是撤销功能实现,
// 只需从 history 数组中取前一次 todos 即可
if (history.length > 0) {
todos = history.pop();
}
TodoStore.emitChange();
}
});
Mutable 对象
在 JavaScript 中,对象是引用类型的数据,其优点在于频繁的修改对象时都是在原对象的基础上修改,并不需要重新创建,这样可以有效的利用内存,不会造成内存空间的浪费,对象的这种特性可以称之为 Mutable,中文的字面意思是「可变」。
对于 Mutable 的对象,其灵活多变的优点有时可能会成为其缺点,越是灵活多变的数据越是不好控制,对于一个复杂结构的对象来说,一不小心就在某个不经意间修改了数据,假如该对象又在多个作用域中用到,此时很难预见到数据是否改变以及何时改变的。
针对这种问题,常规的解决办法可以通过将对象进行深拷贝的形式复制出一个新的对象,再在新对象上做修改的操作,这样能确保数据的可控性,但是频繁的复制会造成内存空间的大量浪费。
|
1
2
3
4
5
6
|
var obj = { /* 一个复杂结构的对象 */ };// copy 出一个新的 obj2// 但是 copy 操作会浪费内存空间var obj2 = deepClone(obj);doSomething(obj2);// 上面的函数之行完后,无论 obj2 是否变化,obj 肯定还是原来那个 obj |
Mutable 和 Immutable 的性能对比
对于 Mutable 的对象的低效率操作主要体现在复制和比较上,而 Immutable 对象就是解决了这两大低效的痛点。
普通的 Mutable 对象的深拷贝操作会将一整份数据都复制一遍,而 Immutable 对象在修改数据时并不会复制一整份数据,而是将变化的节点与未变化的节点的父子关系转移到一个新节点上,类似于链表的结构。从 “复制” 的角度来看,做到了最小化的复制,未变化的部分都是共享的,Mutable 在复制的时候是 “全量”,而 Immutable 复制的是 “增量”,对于内存空间的使用率的比较高低立判。
并且基于每次修改一个 Immutable 对象都会创建一个新的 Immutable 对象的这种特性可以将数据的修改状态保存成一组快照,这也是挺方便的。
再来说说比较操作。对于 Mutable 的对象,如果要比较两个对象是否相等,必须遍历对象的每个节点进行比较,对于结构复杂的对象来说,其效率肯定高不到哪去。对于 Immutable 对象,immutable.js 提供了直接判断两个 Immutable 对象的「值」是否相等的 API。
var map1 = Immutable.Map({a:1, b:1, c:1});var map2 = Immutable.Map({a:1, b:1, c:1});assert(map1 !== map2); // 不同的 Immutable 实例,此时比较的是引用地址assert(Immutable.is(map1, map2)); // map1 和 map2 的值相等,比较的是值assert(map1.equals(map2)); // 与 Immutable.is 的作用一样var mutableObj = {};// 写入数据mutableObj.foo = 'bar';// 读取数据console.log(mutableObj.foo);var immutableObj1 = Immutable.Map();// 写入数据var immutableObj2 = immutableObj1.set('foo', 'bar');// 读取数据console.log(immutableObj2.get('foo')); // => 'bar'var immutableObj1 = Immutable.fromJS({ a: { b: 'c' }, d: [1, 2, 3]});// 读取深层级的数据console.log(immutableObj1.getIn(['a', 'b'])); // => 'c'console.log(immutableObj1.getIn(['d', 1])); // => 2// 修改深层级的数据var immutableObj2 = immutableObj1.setIn(['a', 'b'], 'd');console.log(immutableObj2.getIn(['a', 'b'])); // => 'd'大话immutable.js的更多相关文章
- 深度浅出immutable.js
这篇文章将讲述immutable.js的基本语法和用法. 1.fromJs() Deeply converts plain JS objects and arrays to Immutable Ma ...
- Immutable.js – JavaScript 不可变数据集合
不可变数据是指一旦创建就不能被修改的数据,使得应用开发更简单,允许使用函数式编程技术,比如惰性评估.Immutable JS 提供一个惰性 Sequence,允许高效的队列方法链,类似 map 和 f ...
- Immutable.js尝试(node.js勿入)
最近做一些复杂html常常需要在页面做一些数据处理,常常在想如果 js有list 这种数据结构多少,今天逛github时 发现有Immutable.js 这个项目https://github.com/ ...
- React+Immutable.js的心路历程
这段时间做的项目开发中用的是React+Redux+ImmutableJs+Es6开发,总结了immutable.js的相关使用姿势: Immutable Data 顾名思义是指一旦被创造后,就不可以 ...
- [Javascript] Creating an Immutable Object Graph with Immutable.js Map()
Learn how to create an Immutable.Map() through plain Javascript object construction and also via arr ...
- [Javascript] Manage Application State with Immutable.js
Learn how Immutable.js data structures are different from native iterable Javascript data types and ...
- [Immutable.js] Working with Subsets of an Immutable.js Map()
Immutable.js offers methods to break immutable structures into subsets much like Array--for instance ...
- [Immutable,js] Iterating Over an Immutable.js Map()
Immutable.js provides several methods to iterate over an Immutable.Map(). These also apply to the ot ...
- [Immutable,js] Immutable.Record() as data models
The Immutable.js Record() allows you to model your immutable data much like you would model data wit ...
随机推荐
- 软件工程day4
使用ps制作了一个icon,将在下个版本中添加,用作程序图标. 参与组例会,得知新功能“吐槽墙”将以聊天室类似的社区形式实现. 提出对现有UI的建议: 对目前的登录窗口的UI不做改动,将标题的“用户登 ...
- 文件系统权限引起IIS站点总跳登录页面
今天在IIS上部署一个ASP.NET站点时遇到一个很奇怪的问题,不管访问什么页面,都会跳到登录页面,即使是访问静态文件. 折腾半天,百思不得其解,百整不得其果... 后来突然想到,是不是站点所在文件夹 ...
- Aoite 系列 目录
介绍 本项目从2009年孵化(V->Sofire->Aoite),至今已度过5个年头.一直在优化,一直在重构,一直在商用.有十分完整的单元测试用例.可以放心使用. Aoite on 博客园 ...
- C语言#自动生成四则运算的编程
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <time.h> ...
- 架构设计:前后端分离之Web前端架构设计
在前面的文章里我谈到了前后端分离的一些看法,这个看法是从宏观的角度来思考的,没有具体的落地实现,今天我将延续上篇文章的主题,从纯前端的架构设计角度谈谈前后端分离的一种具体实现方案,该方案和我原来设想有 ...
- 犀利的background-clip:text,实现K歌字幕效果
今天学到了一个新的CSS3属性,更准确的说是属性值,那就是background-clip:text.利用此属性值可以制作出很神奇的效果.可惜只有chrome支持,不过今天可以先来玩玩这个属性. 先来介 ...
- [异常解决] android studio检测不到手机的解决办法——ADB驱动自己安装
用android studio搭建安卓开发环境比eclipse简单的多,仅仅下载一个1个G左右的安装包安装即可. 安装好之后随便建一个hello world工程,想用实体手机调试要点: 选择USB设备 ...
- 使用MYSQL命令直接导入导出SQL文件
很多时候,我们的数据开发都会用到很多开发利器,比如powerdesigner, navicat等这些软件,虽然好用,但是要收费,在公司里面是禁止使用盗版软件的,怕罚款各方面的,所以我们也不敢直接在公司 ...
- 微软官网下载windows系统有点全
第一步:访问:https://www.microsoft.com/zh-cn/software-download/windows10ISO/ 默认就只能下载win10,这怎么行呢.巨硬程序员貌似没做服 ...
- flex Vector
Error: 找不到类型,或者它不是编译时常数: Vector. 或者Type was not found or was not a compile-time constant: Vector. ...