一、PureRender
影响网页性能最大的因素是浏览器的重绘和排版,React的 Virtual DOM 就是为了尽量减少浏览器的重绘和重排版。要优化性能,就需要提高 Virtual DOM 的效率。结合React的渲染过程来看,就是要 防止不必要的渲染。 对此,React提供了一个便捷方法:PureRender。要理解PureRender中的Pure,首先需要理解纯函数。
纯函数的三大原则:
- 给定相同的输入,总是返回相同的输出。
- 过程没有副作用:在纯函数中不能改变外部状态
- 没有额外的状态依赖:方法内的状态都只在方法的生命周期内存活,意味着我们不能在方法内使用共享变量。
React组件本身就是纯函数。React的createElement方法保证了组件是纯洁的,即传入指定props得到一定的Virtual DOM,整个过程是可预测的。PureRender中的 Pure 指的就是组件满足纯函数的条件,即组件的渲染是被相同的 props 和 state 渲染进而得到相同的结果。
PureRender的本质是:重新实现了shouldComponentUpdate生命周期方法,让当前传入的props和state与之前的作 浅比较,如果返回false,组件就不执行render方法。
为什么是浅比较呢?因为深比较太昂贵了。故 PureRender 对object只作了引用比较,并没有进行值比较。在PureRender的源代码中,只对新旧props进行了浅比较。其示例代码如下:
function shallowEqual(obj, newObj){
if(obj === newObj){
return true;
}
const objKeys = Object.keys(obj);
const newObjKeys = Object.keys(newObj);
if(objKeys.length !== newObjKeys.length){
return false;
}
//关键代码,值关注props中每一个是否相等,无需深入判断
return objKeys.every(key=>{
return newObj[key] === obj[key];
})
}
1、运用PureRender
利用createClass构建组件时,可以使用官方的插件react-addons-pure-render-mixin。如下:
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
React.createClass({
mixins: [PureRenderMixin],
render(){
return foo;
}
});
用ES6 classes语法(不支持mixin)一样可以使用这个插件:
import React, { Component } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
class APP extends Component {
constructor(props){
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind;(this);
}
render(){
return foo;
}
}
当然,也可以使用decorator来实现,其中pure-render-deecorator库已经实现了这个功能。
2、优化PureRender
浅比较可以覆盖的场景并不太多,当props或state中有以下几种情况时,无论如何都会触发PuerRender为true(触发组件重新渲染).
(1)直接将props值设置为对象或数组
例如:
实际上在React中,每次调用React组件其实都会创建新的组件。因此,就算每次传入的对象或数组的值是相同的,它们的引用地址却是不一样的。也即是说,上面示例中每次渲染的style其实都是一个新的对象。为style prop设置一个默认值(空对象)也是同样的道理:
解决方法:提前赋值成常量,不直接使用字面量即可。
const defaultStyle = {} ;
这个方法将默认值保存成了同一份引用,故而避免上述问题。
同理,在props中为对象或数据计算新值同样会触发PureRender为true。
- item.val > 30) } />
我们可以马上想到:始终让对象或数组保持在内存中就可以增加命中率。但 保持对象引用 不符合函数式编程的原则,会为函数带来副作用。后面介绍的Immutable.js优雅的解决了这个问题。
(2)设置props方法并通过事件绑定在元素上
示例:
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
this.props.update(e.target.value);
}
render(){
return
}
此处的优化思路为:在构造器内进行事件绑定,避免每次都绑定事件。如果绑定方法需要传递参数,可以考虑通过抽象子组件或改变现有数据结构。
(3)设置子组件
对于设置了子组件的React组件,在调用shouldComponentUpdate时,均会返回true。
class ItemParent extends Component {
render(){
return(
-
hello
);
}
}
上面的Item组件相当于:
显然,Item组件在任何情况下都会重新渲染。要避免这种情况,需要将判断提到父级。也就是给ItemParent设置PureRender。
import React, { Component } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
class ItemParent extends Component {
constructor(props){
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);b
}
render(){
return(
-
hello
);
}
}
二、Immutable
在传递数据时,可以直接使用Immutable Data来进一步提升组件的渲染性能。
Js中的对象一般是可变的(mutable),因为使用了引用赋值,新的对象简单引用了原始对象,改变新的对象会影响到原始对象。使用引用赋值是为了节约内存,但当应用复杂后,就造成了非常大的隐患。为了解决这个问题,一般的做法是使用深拷贝或浅拷贝来避免被修改,但这样又会造成CPU和内存的浪费。Immutable可以很好的解决这些问题。
1、Immutable Data
Immutable Data就是一旦创建,就不能更改的数据。对Immutable对象进行修改、添加或删除操作,都会返回一个新的Immutable对象。
Immutable的原理:持久化的数据结构,即:使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免深拷贝把所有节点复制一遍带来的性能损耗,Immutable使用了结构共享,即:如果对象树中的一个节点发生变化,只修改这个节点和受它影响的父节点,其他节点则进行共享。
2、Immutable的优缺点
优点:
- 降低了“可变”带来的复杂性:值不可变。
- 节省内存:使用共享结构尽量复用内存,没有被引用的对象会被垃圾回收。
- 非常便于实现撤销/重做、粘贴/复制、时间旅行等功能:因为每次数据都是不一样的,只要把这些数据放在一个数组中存储起来,想回退到哪里,就拿出对应的数据,很容易开发撤销/重做功能。
- 并发安全:数据不可变,就不再需要并发锁。
- 拥抱函数式编程:Immutable本身就是函数式编程中的概念。只要输入一致,输出必然一致。这样开发的组件便于调试和组装。
缺点:
Immutable最大的问题就是:容易与原生对象发生混淆。虽然Immutable已经尽量把API设计得与原生对象相似,但还是很难区分 Immutable对象 还是 原生对象。
Immutable中的 Map 与 List 虽然对应的是 JS 中的 Object 和 Array,但操作完全不同。比如取值时要用 map.get('key') 而非 map.key ,要用 array.get(0) 而非array[0]。另外,Immutable每次修改都会返回新对象,很容易忘记赋值。
使用第三方库时,一般需要使用原生对象,同样容易忘记转换对象。
以下办法可以避免类似问题的发生:
- 使用FlowType 或 TypeScript 静态类型检查工具
- 约定变量命名规则,如所有的Immutable类型对象都以 $$ 开头
- 使用 Immutable.fromJS 而不是 Immutable.Map 或 Immutable.list 来创建对象,可以避免Immutable对象和原生对象间的混用。
3、Immutable.js
两个 Immutable 对象可以使用 === 比较,这样是直接比较内存地址,性能最好。但是即使是两个对象的值是一样的,也会返回false。
let map1 = Immutable.Map({a:1, b:2});
let map2 = Immutable.Map({a:1, b:2});
map1 === map2 // false
为了直接比较对象的值,Immutable 提供了 Immutable.is 来作 “值比较”:
Immutable.is(map1, map2) // true
Immutable.is 比较的是两个对象的 hashCode 或 valueOf (对于JS对象)。Immutable内部使用了 trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样做避免了深度遍历比较,性能非常好。
React做性能优化最常用的是 shouldComponentUpdate, 但它默认返回 true,即始终会执行 render 方法,然后做 Virtual DOM 比较,得出是否需要做真实 DOM 的更新,这往往会带来很多不必要的渲染。当然,也可以在 shouldComponentUpdate中使用深拷贝和深比较来避免不必要的 render, 但对性能消耗较大。Immutable.JS提供了简洁高效的判断数据是否变化的方法,只需 === 和 Immutable.is 比较就能知道是否需要执行render, 这个操作几乎零成本,可以极大提高性能。
三、key
子组件如果是一个数组或者迭代器的话,那么必须有一个唯一的 key 属性,用于标识当前项的唯一性。key 相同的情况下,React只会渲染第一个相同 Key 的项。
key用于做 Virtual DOM diff,可以提高性能。key的取值原则为:独一无二,能不用遍历或随机值就不用,除非列表内容也并不是唯一的表示,且没有可以相匹配的属性。