距离五一放假还剩1小时,领导突然走到我位置上,再一次和我提起了之前页面遇到的性能问题,说有时间的话可以先想想办法,不急着处理。这个页面是一个详情页面,页面有很多表单和输入域,这个性能问题我之前看过,根本原因在于需要渲染的dom节点数量太多了,之前是使用了antd里的form表单组件,单是几个简单的输入框都会生成很多dom节点,这些节点是框架生成的,dom节点数量太多造成的页面刚加载时没响应这个怎么解决呢?
首先,我查了一下这个页面的表单组件渲染出来的dom节点有上万个,页面刚加载进来时就要渲染上万个dom节点,而创建dom节点的开销大,所以页面在5秒以后才有响应,现在我模拟一下在页面挂载上万个节点,直接把antd官网里的例子搬过来。
然后查看一下这个表单有多少个dom节点,使用getElementsByTagName('*')来统计:
打印出来这个表单组件有188个节点,对于用户来说只有12个可输入域加1个按钮,但是渲染出来的节点有将近两百个,如果有上百个可表单域,那节点数量可想而知了。接着我们使用React.Profiler来查看组件挂载到页面上花了多少时间:
这里可以看到这个表单组件挂载到页面上用了226ms,我们现在渲染100个表单,让节点达到上万个,再来看看渲染需要的时间。
虽然Profiler组件提示首次挂载页面只有3.58秒,但从页面效果上看,点击完按钮后页面就已经卡死,从按钮上移开鼠标还呈现悬浮在按钮上的小手图标,直到渲染了表单在页面上点击输入框能获取焦点时,已经过去了9秒,在这9秒内页面无响应(跟测试性能组件的结果差得有点远啊。。)
现在重现了性能问题,解决的话我是用了虚拟滚动库react-window,它不会一次性把需要的dom节点全部渲染出来,而是只渲染必要的部分(渲染当前视口的前几个和后几个),因此可以大量减少渲染的dom节点,以此来提高页面加载速度。
AutoSizer可获取到父元素的宽高直接传给FixedSizeList,也可以不使用AutoSizer按自己的需求给宽高。如果每一项的高度不是固定的,可使用VariableSizeList组件,这里以FixedSizeList为例。
可以看到,渲染100个表单已经很快,因为首次渲染表单只渲染570个节点,不是上万个节点了。
我们可以再给每个表单设置表单值,给第一个表单的所有表单值为‘字段名称-1’,给第二个表单的表单值为'字段-2'以此类推。在Row组件里其实可以知道当前渲染的是第几个表单,我们把这个index传给表单组件,表单组件在组件加载完后调用form.setFieldsValue方法设置表单值就可以了
表单值成功设置好了,用了虚拟滚动减少dom节点数量渲染页面也不再卡顿,大家遇到这种问题也可以尝试用下虚拟滚动来解决~