最近在处理大list(infinite scroll view)时,发现元素多了之后react渲染开始变得卡顿,想找一找问题出在哪里了,在参考了一些博客和做了一番研究之后,给大家来个总结。redux的store中的数据量太大,肯定是会影响性能的,但是跟其本身占的内存并没有太大关系,影响的是一些别的东西,请往下看。
1.action触发大量reducer的开销
store过分庞大,就是意味着state树的分支很多,也就导致节点对应的reducer特别多,派发一个action,会把所有的reducer都跑一遍,最后触发对应的action修改state的操作,这对性能会有一定影响,有的人说用combineReducers,用了这个action还是会把所有reducer跑一遍,combineReducers只是把state节点分离了而已,用来关注当前state节点的操作,跟action没关系,如果你在两个reducer里面有重名的action type,他两都会触发,证明还是会都跑一遍。这里推荐使用 redux-ignore https://github.com/omnidan/redux-ignore,可以防止触发跟某reducer不相关的其他action。
2.state树的内存开销(没有太大关系)
所谓的state tree就是一个json object。在栈里面存了一个引用,在堆里面存了一个对象,就算state树很大层级很多,基本也不可能达到G级别的,况且,在正确设计state树的情况下,这是不可能也不合理的,redux推荐设计尽可能最小化的state树,只放必要的节点,其他的应该交由组件自己的state来维护。
3.通过connect()连接store的subscriber,在store改变时被触发的开销
如果每个UI component都连接一堆props到store自然是很恐怖的,因此redux推荐只在最外部的组件中做connect,也就是container,然后在container中引用其他component来渲染,component自身的一些状态改变可以用react自己的state来维护。
4.渲染大型虚拟DOM的开销
首先react-redux框架中的connect已经帮我们优化了最外层的container,只会渲染改变的部分,至于container中的其他component,可以通过shouldComponentUpdate()来判断是否重新渲染组件,这也是可以轻松完成的性能优化。但是当处理大list时(上拉无限加载),在dom元素越来越多时,渲染还是明显会出现卡顿,虽然react只会将虚拟dom改变的部分渲染到真实dom(暂且相信react在大量数据下的diff算法,官方说很高效,事实也确实如此),但是如果改动部分的dom节点本身就是一个庞大的量级(比如一次性改变或插入一万个dom节点),那么渲染就会卡顿,redux作者做过一个压力测试,结论是渲染一万个node时,操作会有轻微的延迟。当然,正常的app应该是不会有上千node的,假如真的有,那绝大部分都不是用户可见的(比如一个很长很长的scroll view,切换tab回来的时候还保持滚动位置,不自动刷新dom,就会导致一次性重新插入大量dom)。这样情况下,可以用 react-infinite https://github.com/seatgeek/react-infinite,原理是只渲染用户能看见,以及即将看见的。其余的都合并起来放在一个dummy node里。
结语:最好的优化就是不碰到瓶颈前别瞎优化