React Native 组件性能优化
前言
从以往的经验和实践中,我们知道影响性能最大的因素是界面的重绘,React 背后的 Virtual DOM 就是尽可能地减少重绘。
对于性能优化这个主题,我们往往会基于 "不信任" 的前提,即我们需要提高 React Virtual DOM 的效率。从 React 渲染过程看,如何防止不必要的渲染可能是最需要取解决的问题。针对这个问题,React 官方提供了一个便捷的方法来解决,那就是 PureRender。
纯函数
要理解 PureRender 中的 Pure,要从函数式编程的的基本概念 "纯函数"讲起。纯函数由三大原则构成。
- 给定相同的输入,他总是返回相同的输出
对于一个方法,只要我们传入的值是固定,无论做多少次调用,结果都是一样。有些方法不依赖于你传入的参数,如 Math.random(),不传任何参数到方法中,该方法依然总是会输出不同的结果,这种方法也是纯函数。还有常用的 slice 和 splice 方法,slice在参数一致的时候结果都是一致的,splice方法的执行结果会改变原来数组,对于程序来说,splice 的隐藏行为是危险的,所以 splice 不是纯函数。
- 过程没有副作用
在纯函数中我们不能改变外部状态。
- 没有额外的状态依赖
方法内的状态都只在方法的生命周期内存活,意味着不能在方法内使用共享变量,这会给方法带来不可知因素。
纯函数也是函数式编程的基础,他完全独立于外部状态,这样就避免了因为共享外部状态而导致的 bug。
纯函数非常方便方法级别的测试以及重构,可以让程序具有良好的扩展性及适应性。
React 就是函数式编程,React 组件本身就是纯函数,可以表示为 UI = f(data),data 包括props 和 state,改变 data,就能改变 UI,而不是像 JQuery 一样直接操纵 UI。
可以通过拆分组件为自组件,进而对组件做更颗粒度的控制。只是函数式编程的魅力之一,保持纯净状态,让方法或组件更加专注 (focused),体积更小 (small),更独立(independent),更具有复用性 (reustability)。
PureRender
pureRender 指的就是组件满足春寒书条件,即组件的渲染是被相同的 props 和 state 渲染进而得到相同的结果。
- PureRender 的本质
React 生命周期中有一个 shouldComponentUpdate 方法,如果返回 true,表示需要渲染,返回 false 表示不需要渲染。
- 运行 PureRender
React 新版本提供一个 PureComponent,PureComponent 内部重新实现 shouldComponentUpdate 方法,让当前传入的 props 和 state 与之前的作浅比较,,浅比较对 object 只作了引用比较,并没有做值比较,如果返回 false,那么组件就不会执行 render 方法。
- 优化 PureRender
- 直接为props设置对象或数组
每次调用 React 组件其实都会重新创建组件,就算传入的数组或对象的值没有改变,它们引用的地址也会发生改变。
<TestComponent
style={{background: 'white'}}
/>
我们可以把传入的数组或对象保存成一份引用。
<TestComponent
style={styles.container}
/>
const styles = StyleSheet.create({
container: {
background: 'white'
},
});
- 设置 props 方法并通过事件绑定在元素上
onPress() {
}
<TestComponent
onPress={this.onPress.bind(this)}
/>
这样写,每一次渲染都会重新绑定 onPress方法, 不要让方法每一次都绑定,因此把绑定移动到构造器内。
constructor(props) {
super(props);
this.onPress = this.onPress.bind(this);
}
onPress() {
}
render() {
<TestComponent
onPress={this.onPress}
/>
}
Immutable
在传递的数据的时候,可以直接使用 Immutable Data 来进一步提高组件的渲染性能。
-
Immutable Data
Immutable Data 就是一旦创建,就不能再更改的数据。对 Immutable 对象进行修改,添加或者删除操作,都会返回一个新的 Immutable 对象。Immutable 实现的原理是持久化的数据结构,也就是使用旧数据创建新数据,要保证就数据同时可用切不变。同时为了避免深拷贝把所有节点都复制一遍带来的性能损耗,Immtable 使用了结构共享,即如果对象树中一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点进行共享。
- Map:键值对集合,对应于Object。
- List:有序可重复的列表,对应于Array。
- ArraySet:无序切不可重复的列表。
- Immutable 的优点
- 降低了 "可变" 带来的复杂度。
- 节省内存。Immutable 使用结构共享尽量复用内存。没有被引用的对象会被垃圾回收。
- 撤销/重做,复制/粘帖,甚至时间旅行这些功能做起来都变得简单。因为每次数据都是不一样的,那么只要把这些数据都放到一个数组里存储起来,想回退到哪里,就拿出对应的数据。
- 并发安全。Immutable 数据天生不可变,但 js 是单线程运行,现在并没有什么用。
- 拥抱函数式编程。只要输入一致,那么输出必然一致,这样开发的组件更易于调试和组装。
-
使用 Immutable 的缺点
容易与原生对象混淆。 -
Immutable.is
为了直接比较对象的值,Immutable提供了Immutable.is 来作"值比较"
const map1 = Immutable.Map({a: 1, b: 1})
const map2 = Immutable.Map({a: 1, b: 1})
map1 === map2; // => false
Immutable.is(map1, map2); // => true
Immutable.is比较的是两个对象的hashCode 或 valueOf (对于JS 对象),由于Immutable 内部使用 trie 数据结构来存储,只要两个对象 hashCode 相等,值就是一样的。
5.Immutable 和 PureRender
React 做性能优化最常用的就是 shouldComponentUpdate 方法,但他默认返回true,即始终会执行 render 方法,然后做 Virtual DOM比较,并得出是否需要做真实 DOM 更新,这里往往带来不必要的渲染,
当然,我们可以在 shouldComponentUpdate 中使用深拷贝和深比较来避免无必要的 render,但是深拷贝和深比较一般都是非常昂贵的选择。
Immutable.js 提供了简洁,高效的判断数据是否变化的方法,只需要 is 比较就能知道是否需要执行 render,而这个操作几乎零成本,所以可以极大提高性能。修改后的 shouldComponentUpdate 是这样的。
import React, {
Component
} from 'react';
import Immutable from 'immutable';
class BaseComponent extends Component {
shouldComponentUpdate(nextProps, nextState = {}) {
return !Immutable.is(Immutable.fromJS(this.props), Immutable.fromJS(nextProps))
|| !Immutable.is(Immutable.fromJS(this.state), Immutable.fromJS(nextState));
}
}
export default BaseComponent;
而在自定义组件你只需要继承BaseComponent,就可以拦截不必要的渲染。
class App extends BaseComponent {
}
-
key
写动态组件的时候,需要给动态子组件添加 key props,否则会报一个警告。Key 要保持惟一,稳定。