React 性能调优总结
首先要说一个库: why-did-you-update
, 地址:why-did-you-update, 利用这个库可以在页面上快速看到多余渲染的问题:
因为多数情况下我们在React组件当中是不会去写shouldComponentUpdate
这个hook来避免多余渲染的,所以就造成了少量的性能浪费。虽然优化是个漫长的道路,过早优化是邪恶的,但做还是要去做的。
下面讲一下基本的一些手段
shouldComponentUpdate
在React当中,每一次的setState
操作,都会让Virtual DOM
去做diff操作,虽然虚拟DOM的计算很快,但是随着组件越来越多,结构越来越复杂,当你改变某个简单的state时,就会造成连带很多Component的重新render
,所以我们可以在组件内部去添加shouldComponentUpdate
钩子来告诉React是否要更新组件
PureComponent
当然我们在实际开发过程当中,由于数据的复杂程度来说,基本是不会去写shouldComponentUpdate
hook,这时就可以去使用PureComponent
类来声明组件。
PureComponent
的前身是PureRenderMixin
,和Component的区别就在于组件在render
之前会自动执行一次shallowEqual(浅比较)
,就相当于组件的第一层props和state的数据如果没有发生改变,render就不会去执行,从而减少了不必要的渲染。
根据React源码,如果组件是纯组件(Pure Component),那么一下比较是很容易理解的:
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
为什么说是浅比较呢?这个很好理解,js的引用数据类型就很好的说明了这一点:
{} === {} //false
[] === [] //false
当然,你也可以在shouldComponentUpdate
中自己来写深比较,在数据结构相对简单的情况下:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.user.id === props.user.id;
}
如果是在PureComponent当中写了这个钩子,那么它就会被优先执行。
当然,在数据结构和嵌套比较深的情况下,这个方案也就不太管用了,所以,我们在前期定义数据结构时也是一个很重要的环节,可以去避免不必要的渲染。
Immutable or Immer
Facebook在2014年就推出了这个库: Immutable.js,用来使数据持久化。在数据创建后,就不得去改变,任何的增删改操作都是true一个新的Immutable
对象:
import { Map } from "immutable";
const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1 !== map2; // true
map1.get('b'); // 2
map2.get('b'); // 50
map1.get('a') === map2.get('a'); // true
ImmutableJS
最大的两个特性就是: immutable data structures
(持久性数据结构)与 structural sharing
(结构共享),持久性数据结构保证数据一旦创建就不能修改,使用旧数据创建新数据时,旧数据也不会改变,不会像原生 js 那样新数据的操作会影响旧数据。而结构共享是指没有改变的数据共用一个引用,这样既减少了深拷贝的性能消耗,也减少了内存。比如下图:
ImmutableJS
的API过于复杂,而且我也没有用redux
,而是采用了Mutable
的Mobx
,看见Mobx
的作者写了一个库Immer,相对于ImmutableJS
比较简单:
import produce from "immer"
/** * Classic React.setState with a deep merge */
onBirthDayClick1 = () => {
this.setState(prevState => ({
user: {
...prevState.user,
age: prevState.user.age + 1
}
}))
}
/** * ...But, since setState accepts functions, * we can just create a curried producer and further simplify! */
onBirthDayClick2 = () => {
this.setState(
produce(draft => {
draft.user.age += 1
})
)
}
代码上的优化
少用bind
每次bind都会返回一个新函数,重复创建静态函数会浪费性能。最好直接使用箭头函数绑定或者利用闭包直接把处理函数传入子组件
setState优化
在我们去setState时,最好用新值去覆盖旧值,而不是修改原值。 对于数组,我们采用es6的spread语法:
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
对于对象,我们采用Object.assign或spread:
this.setState({
a: Object.assign({}, this.state.a, {b: '2222'})
})
//或者
this.setState({
a: {...this.state.a, {b: '222'}}
})
不要在PureComponent组件的props使用直接赋值的方式
style={ { width: '100px' } } 这样的做法传入组件会造成重复渲染
这样的方式会使shallowEqual
一定返回false
正确的方式:
const YourStyle = { width: '100px' }
return (
<YourComponent style={YourStyle}></YourComponent>
)
或者我们直接就用上面说到的Immutable.js 或者 Immer.js 来处理