hushicai / hushicai.github.io

Blog
https://hushicai.github.io
27 stars 1 forks source link

React Native Web化的一些性能问题 #79

Open hushicai opened 4 years ago

hushicai commented 4 years ago

本文是基于一个聊天系统项目,主要针对消息列表进行分析。

技术栈:

由于是公司项目,这里就不贴源码了。

hushicai commented 4 years ago

滚动长列表

优化前

image

滚动消息列表时,FlatList在每次update任务中,所有已渲染的CellRenderer都会被重新update。 这个有点不寻常,按道理已经渲染的Cell不应该update,因为消息数据并没有变化。 检查了一下代码,发现原来ListItem的props上有一个onItemLayout属性,这个属性在每次reconciler的过程中都是变化的,这就会导致PureComponent失效,每次都会进行update。

优化后

避免使用闭包,因为它每次都会生成一个新的函数,改为通过public class fields syntax来声明函数。

image

可以看到,优化后,已渲染过的CellRenderer的颜色条变小了很多,整体的update时间从200多毫秒减少到60多毫秒!

hushicai commented 4 years ago

发送消息

优化前

image

发送消息时也存在同样的问题,FlatList在update时,所有已渲染的CellRenderer也被重新渲染了。 检查了一下代码,发现原来传了一个index属性,导致在发送消息时,每条消息的index都变了,所以ListItem都会被update。

备注:这里的FlatList是一个inverted FlatList,所以在prepend消息时,index会整体往后移,所以导致整个列表都重绘了。

优化后

去掉index,依赖index的逻辑改为通过message id去查找计算出来。

image

可以看到,优化后,只有最新的一条消息会被重新update,因为它依赖了NextMessage和PrevMessage,而其他的消息就不会被update,整体update时间从200多毫秒减少到50多毫秒!

hushicai commented 4 years ago

加载历史消息

优化前

image

消息列表滚到底部后,竟然还有一个将近600毫秒的update任务。 刚开始有点不明觉厉,按道理所有消息都已经加载完了,加载历史消息时,如果没有拉到新消息,应该只会更新ListHeader,为什么会出现这么长的一个update任务呢?

image

检查了一下redux log才发现,原来是加载历史消息的逻辑有点问题。 如果本地现在已经有100条消息,当首次触发加载历史消息时,因为offset丢了,那就会一次性拉100 + 10 = 110条消息,然后query成功后,覆盖掉本地所有消息。 React收到新消息后,就会对整个消息列表进行update。

备注:因为offset没有做本地持久化,所以用户首次进入到聊天窗口,offset丢了。

优化后

这是offset算法设计上的失误,改成按最后一条消息的message id来拉取。

image

可以看到,现在确实只update了ListHeader,更新任务的时间从600毫秒减到6毫秒!

hushicai commented 4 years ago

以上性能问题,其实是React常见的bad case,总结一下: