当PureComponent遇上ImmutableJS ,让React应用性能发挥到极致
2017-09-14 13:57
671 查看
痛点
转载https://wulv.site/2017-08-22/purecomponent-immutablejs.html在我们的印象中,
React好像就意味着组件化、高性能,我们永远只需要关心数据整体,两次数据之间的
UI如何变化,则完全交给
React的
Diff算法去做。以至于我们很随意的去操纵数据,
shuouldComponentUpdate也懒得去写,反正不写也能正确渲染。但随着应用体积越来越大,会发现页面好像有点变慢了,特别是组件嵌套比较多,数据结构变复杂的情况下,随便改变一个表单,或者对列表做一个筛选都要耗时
100ms左右,这个时候我们就需要优化了!当然如果如果没有遇到性能瓶颈,完全不用担心,过早优化是邪恶的。这里我们提供一个很简单的方案来让
React应用性能发挥到极致,为了照顾基础比较弱的读者,我们先回顾一下
JavaScript变量类型和
React渲染机制,如果你是老鸟可以直接跳过。
变量类型
JavaScript的变量类型有两类:基本类型:6 种基本数据类型,
Undefined、
Null、
Boolean、
Number、
String、
Symbol
引用类型:统称为
Object类型,细分为:
Object类型、
Array类型、
Date类型、
RegExp类型、
Function类型等。
举个例子:
12345678910 | // 基本类型let a = 'test';let b = a;b = 'test1';console.log(a); // test// 引用类型let p1 = { name: 'neo' };let p2 = p1;p2.name = 'dave';console.log(p1.name); // dave |
p1的对象,把
p1赋值给
p2,修改
p2的
name属性,结果
p1的属性也修改了,也就是说
p1与
p2其实是公用一个引用,虽然这样做可以节约内存,但当应用复杂后,就需要很小心的操作数据了,因为一不注意修改一个变量的值可能就影响到了另外一个变量。如果我们想要让他们不影响,就需要拷贝出一份一模一样的数据,拷贝又分浅拷贝与深拷贝,浅拷贝只会拷贝第一层的数据,深拷贝则会递归所有层级都拷贝一份,比较消耗性能。
React
在 React中,每次
setState,
Virtual DOM算法会计算出前后两次虚拟
DOM对象的区别,再去修改真实需要修改的
DOM。由于
js计算速度很快,而操作真实
DOM是昂贵的消耗,
Virtual DOM算法避免了没必要的真实
DOM操作,所以
React性能很好。但随着应用复杂度的提升,
DOM树越来越复杂,大量的对比操作也会影响性能。比如一个
Table组件,修改其中一行
Tr组件的某一个字段,
setState后,其他所有行
Tr组件也都会执行一次
render函数,这其实是不必要的。我们可以通过
shuouldComponentUpdate函数决定是否更新组件。大部分时候我们是可以知道哪些组件是不会变的,根本就没必要去计算那一部分虚拟
DOM。
PureComponent
React15.3中新加了一个类,前身是
PureRenderMixin,和
Component基本一样,只不过会在
render之前帮组件自动执行一次shallowEqual(浅比较),来决定是否更新组件,浅比较类似于浅复制,只会比较第一层。使用
PureComponent相当于省去了写
shouldComponentUpdate函数,当组件更新时,如果组件的
props和
state都没发生改变,
render方法就不会触发。如果
props和
state虽然值没变,但引用变了,就会造成虚拟
DOM计算的浪费,如果值改了,但引用没改,又会造成不渲染,所以需要很小心的操作数据。
Immutable.js
Immutable.js是 2014年出的持久性数据结构的库,数据一旦创建,就不能再被更改,任何修改或添加删除操作都会返回一个新的
Immutable对象。可以让我们更容易的去处理缓存、回退、数据变化检测等问题,简化开发。
1234567 | import { Map } from "immutable";const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 });const map2 = map1.set('b', 50);map1 !== map2; // truemap1.get('b'); // 2map2.get('b'); // 50map1.get('a') === map2.get('a'); // true |
ImmutableJS提供了7种不可修改的数据类型:
List、
Map、
Stack、
OrderedMap、
Set、
OrderedSet、
Record。其中比较常用的有
List、
Map和
Set:
Map:无序的
key、
value集合,对应
js的
Object类型
List:有序且可以重复的集合,对应
js的
Array类型
Set:没有顺序且不能重复的列表
这些类型提供了大量的方法去更新、删除、添加数据,极大的方便了我们操纵数据。除此之外,还提供了原生类型与
ImmutableJS类型判断与转换方法:
12345678 | import { fromJS, isImmutable } from "immutable";const obj = fromJS({ a: 'test', b: [1, 2, 4]}); // 支持混合类型isImmutable(obj); // trueobj.size(); // 2const obj1 = obj.toJS(); // 转换成原生 `js` 类型 |
ImmutableJS最大的两个特性就是:
immutable data structures(持久性数据结构)与
structural sharing(结构共享),持久性数据结构保证数据一旦创建就不能修改,使用旧数据创建新数据时,旧数据也不会改变,不会像原生
js那样新数据的操作会影响旧数据。而结构共享是指没有改变的数据共用一个引用,这样既减少了深拷贝的性能消耗,也减少了内存。比如下图:
我需要改变红色节点的值,生成的新值改变了红色节点到根节点路径之间的所有节点,也就是所有青色节点的值,旧值没有任何改变,其他使用它的地方并不会受影响,而超过一大半的蓝色节点还是和旧值共享的。在
ImmutableJS内部,构造了一种特殊的数据结构,把原生的值结合一系列的私有属性,创建成
ImmutableJS类型,每次改变值,先会通过私有属性的辅助检测,然后改变对应的需要改变的私有属性和真实值,最后生成一个新的值,中间会有很多的优化,所以性能会很高。
案例
首先我们看看只使用 React的情况下,应用性能为什么会被浪费,代码地址:下载,这个案例使用
create-react-app,检测工具使用
chrome插件:React
Perf。执行
12 | yarnyarn start |
Print Wasted那一项里,渲染
Tr组件浪费了5次:
无论是添加,删除操作,都会浪费
n-1次
render,因为
App组件的整个
state改变了,所有的组件都会重新渲染一次,最后对比出需要真实
DOM的操作。如果把
Table组件和
Tr继承的
Component改成
PureComponent,那么,
Tr组件每次更新都会进行一次
shallowEqual比较,修改操作就没有了浪费,可是添加和删除操作却无效了,添加的操作是:
1234567 | add = () => { const { data } = this.state; data.push(dataGenerate()) this.setState({ data }) } |
data.push并没有改变
data的引用,所以
PureComponent的浅比较直接返回了
true,不去
render了。这并不是我们想要的,所以如果使用
Component必定带来性能浪费,使用
PureComponent又需要小心的操纵数据,不然一不小心就出错了。
这个时候,
ImmutableJS就可以显示出它的威力了,因为它只会改变需要改变的那条路径,我们看看修改后的例子:代码地址:下载,执行上面例子同样的操作,可以看到:
添加,删除,修改操作,没有一次浪费。可以看出,
PureComponent与
ImmutableJS简直是天生一对啊,一个只有
props或
state修改了才会
re-render,一个当且仅当修改需要修改的路径,如果结合
redux,那就更加完美了。因为
redux的
reducer每次返回的必须是一个新的引用,有时候我们必须使用
clone或者
assign等操作来确保返回新引用,如果使用
ImmutanleJS根本就不需要
lodash等函数库了,比如我使用
redux + immutable + react-router + express写了一个稍微复杂点的例子:下载,可以看到
store的状态是:
12345678910111213141516 | { loading: false, tableData: [{ "name": "gyu3w0oa5zggkanciclhm2t9", "age": 64, "height": 121, "width": 71, "hobby": { "movie": { "name": "zrah6zrvm9e512qt4typhkt9", "director": "t1c69z1vd4em1lh747dp9zfr" } } }], totle: 0} |
width的值为90,可以使用以下几种方式:
12345678910111213141516171819202122232425262728 | // payload = { name: 'gyu3w0oa5zggkanciclhm2t9', width: 90 }// 1. 使用深拷贝 updateWidth(state, payload) { const newState = deepClone(state); return newState.tableData.map(item => { if (tem.name === payload.name) { item.width = payload.width; } return item; }); }// 2. 使用Object.assign updateWidth(state, payload) { return Object.assign({}, state, { tableData: state.state.map(item => { if (item.name === payload.name) { return Object.assign({}, item, { width: payload.width }); } return item; }) }) }// 3. 使用ImmutableJS updateWidth(state, payload) { return state.update('tableData', list => list.update( list.findIndex((item) => item.get('name') === payload.name), item => item.set('width', payload.width))); } |
re-render,
而
Object.assign会浅复制第一层,虽然不会造成
re-render,但浅复制把其他的属性也都复制了一次,在这里也是很没有必要的,只有使用
ImmutableJS完美的完成了修改,并且代码也最少。
优势与不足
可以看出, ImmutableJS结合
PureComponent可以很大程度的减少应用
re-render的次数,可以大量的提高性能,并且提供了大量的类似原生
JS的方法,还有
Lazy Operation的特性,完全函数式编程,很容易实现
Redo/Undo历史回顾。但还是有一些不足的地方:
获取组件属性必须用
get或
getIn操作(除了RecordR类型),这样和原生的
.操作比起来就麻烦多了,如果组件之前已经写好了,还需要大量的修改。
ImmutableJS库体积比较大,大概56k,开启
gzip压缩后16k。
学习成本。
难以调试,在
redux-logger里面需要在
stateTransformer配置里执行
state.toJS()。
最佳实践
其实,重要的是编程者需要有性能优化的意识,熟悉 js引用类型的特性,了解事情的本质比会使用某个框架或库更加重要。用其他的方法也是完全可以达到
ImmutableJS的效果,比如添加数据可以使用解构操作符的方式:
123456 | add = () => { const { data } = this.state; this.setState({ data: [...data, dataGenerate()] }) } |
还有两个轻量库可以实现不可变数据结构:seamless-immutable或者immutability-helper,只不过原理完全不一样,效率也没那么高。
避免大量使用
toJS操作,这样会浪费性能。
不要将简单的
JavaScript对象与
Immutable.JS混合
结合
redux的时候,要使用
import { combineReducers } from 'redux-immutablejs';,因为
redux的
combineReducers期望
state是一个纯净的
js对象。
尽量将
state设计成扁平状的。
展示组件不要使用
Immutable数据结构。
不要在
render函数里一个
PureComponent组件的
props使用
bind(this)或者
style={ { width: '100px' } },因为浅比较一定会对比不通过。
参考链接
Immutable.js,persistent data structures and structural sharing
immutable.js
is much faster than native javascript
Immutable
详解及 React 中实践
相关文章推荐
- 技术文章 | 如何将 HTML5 性能发挥到极致
- 如何将 HTML5 性能发挥到极致
- React同构与极致的性能优化
- CCM - 极致发挥STM32F4性能的利器
- React 应用的性能优化思路
- 【jiasuba】笔者分析:快速优化Wi-Fi 发挥网络极致性能
- React性能优化 PureComponent 使用指南
- 如何将 HTML5 性能发挥到极致
- CCM ——极致发挥STM32F4性能的利器
- 这些应用可以让你的kindle发挥到极致
- Nginx+PHP 优化配置 发挥性能极致
- 【翻译&摘抄】React 应用性能调优
- CCM - 极致发挥STM32F4性能的利器
- 如何将HTML5引擎LayaAir的性能发挥到极致
- React-用ImmutableJS提高性能
- 高级排序-快速排序-使用插入排序来处理小于10个数据项的子数组,使快速排序性能发挥到极致。
- 技术文章 | 如何将 HTML5 性能发挥到极致
- React同构与极致的性能优化
- React性能优化 PureComponent
- 发挥ThinkPHP的极致性能。大型项目代码部署