列表页性能优化实践

背景

小编在在项目中遇到了大数据量的列表出现严重卡顿的情况,其中比较明显的两个反馈:Table选中操作响应迟缓(3s以上),第二个是切换分支之后数据渲染的很慢(8秒以上,数据请求才50ms)。

对其历史代码排查发现在代码设计存在一些缺陷,在这里记录下,大家开发的时候注意规避。

性能优化过程

首先介绍下如何解决这个性能问题。

解决性能问题最关键一点就是如何量化性能指标,只靠自我感知很难明确的优化的效果以及中间的渲染过程,这里介绍下我是如何计算渲染各个阶段的耗时的。

Table 选中checkbox的选中效果展示的计时打点

上面有五个计时点,最终的触发顺序是 1 → 2 → 3 → 0 → 0 → 4 其中 3 → 0 → 0 → 4 的环节耗时比较久,大概在2s+,这里两次0-render是由于列表页的loading是基于 dva-loading 实现的,会触发一次render。

实际数据在第一次0-render就已经OK了,但是渲染过程比较慢。

这里有以下方面原因:

1、Table 在旧版本是不支持虚拟滚动的,且每次checkbox的状态变化会引起所有Table Row的重新渲染,且checkbox的选中动画也比较消耗性能,会导致响应缓慢,官方Github Issue中有介绍github.com/ant-design/...

2、代码设计的内部state 直接存储了列表原数据,并且选中selctedKey存储的是原数据,反选也存储原数据,不是row-key,Table组件接受的selectedKey也要经过useMemo进行map提取Key,计算复杂,数据体积大,给React VirtualDom 的diff过程带来了很大的压力

3、dva-loading是基于effect 接口请求实现的,是个root redux store的状态,会引起大量组件的重绘,产生很多无用性能开销

切换分页数据渲染很慢

上述八个计时点,7 → 8 耗时大,和第一个问题的原因是一样的,其中比较特殊的是 4 → 5 的耗时也有1秒多,经过排查发现以下性能原因:

1、目前Redux的全局store十分冗余,导致其diff过程变长

2、useEffect 和 useMemo 的数据依赖存在交叉,且diff计算过大,出现useEffect和useMemo多次串行渲染的情况,没能实现状态合并渲染

3、onPageChange和选中态实时同步到了父组件,导致父组件也频繁render,增加diff复杂度

优化总结

1、针对 antd 本身的性能缺陷,官方推荐升级antd Table 4版本,支持虚拟渲染;社区的大佬推荐关闭checkbox的过渡动画,或者直接使用 原生 < input type='checkbox' /> 替换checkbox,避免选中态变化引起Table的重绘。这两个方式成本较大

2、将Table 的数据从 Redux store 迁移到Table组件内部,列表数据并不是共享数据,因此没有必要放到全局store中,导致store数据变化传递到组件内部的链路长且耗时长。

3、Table 的loading交互不要使用dva-loading实现,使用Table-loading api即可,基于redux action 在 Table 组件内部维护即可,避免Table分页变化,引起一系列父组件的render。

css 复制代码
listApplication: generateEffect('departCooperation/listApplication', { loading: false }),

4、由于数据请求迁移到内部,因此其分页数据之后的数据变化就不需要依赖useEffect和useMemo了,直接在promise.then之后直接计算所有依赖数据,避免出现useEffect和useMemo出现交替串行渲染的问题

5、精简useState维护的数据,移除 includeRows、excludeRows、defaultSelectedRows 原数据存储,改用projectId存储,精简useMemo和useMemo计算逻辑,移除Map类型的数据维护

6、将提交按钮从容器组件移动到Table组件内,将提交处理函数从容器组件传递到Table组件,避免value-change无用频繁触发。

规范沉淀

1、列表类数据禁止使用redux Store存储,非全局核心复用数据不能使用 redux store

2、数据state的数据量要控制大小,尽可能使用常规 JavaScript 的数据类型,尽可能使用非引用类型数值

3、组件封装注意数据流的管理,value-change 模式是指子组件不具有主动调用的能力,数据实时同步,十分适合UI复用;handler-props则是子组件控制调用时机,主动调用通过props传递进来handler函数逻辑,并且补全handler内部参数,这些参数一般为子组件内部状态,思想贴近函数柯里化,且更加容易复用逻辑。这两种模式各有优缺点。

这里列举下使用场景 value-chang模式适合UI组件,即组件是最小粒度的元素,例如各大UI库;handler-props 则更加使用业务组件的封装,可以屏蔽内部逻辑细节,只关注在handler的处理逻辑的适配,明确数据含义。

4、关注 antd 组件提供的各种API参数,除非组件提供的API完全不能满足需要,否则必须使用官方API实现,因为这些规范是最具有共识的,只要大家使用这些API,其代码风格和设计会自然而然保持一致的,而且十分有利于后续版本的升级,无需额外的改造成本。

相关推荐
美狐美颜sdk5 小时前
跨平台直播美颜sdk集成攻略:Android、iOS与Web的统一方案
android·前端·ios
Airser5 小时前
npm启动Taro框架报错
前端·npm·taro
Anlici6 小时前
连载小说大学生课设 需求&架构
前端·javascript·后端
2501_938769997 小时前
React Server Components 进阶:数据预取与缓存
前端·react.js·缓存
蒜香拿铁7 小时前
Angular【基础语法】
前端·javascript·angular.js
xiaoxiao无脸男8 小时前
纯css:一个好玩的按钮边框动态动画
前端·css·css3
rookie_fly8 小时前
基于Vue的数字输入框指令
前端·vue.js·设计模式
元直数字电路验证8 小时前
ASP.NET Core Web APP(MVC)开发中无法全局配置 NuGet 包,该怎么解?
前端·javascript·ui·docker·asp.net·.net
rexling19 小时前
【Spring Boot】Spring Boot解决循环依赖
java·前端·spring boot
我有一棵树9 小时前
Vue 项目中全局样式的正确写法:不要把字体和主题写在 #app 上
前端·javascript·vue.js