前言
这篇文章是根据公司的业务需求我是这样封装组件关于性能优化的续写。
看一下表格的大致内容:
上次我分享了我是如何根据公司业务封装一个树状结构的多功能表格组件,整体来看能满足前期需求。但随着业务的扩大,随之而来的考验就是性能上能否满足用户。
接下来,我将围绕着性能把自己的所思所想做个记录📝分享出来~
觉得不错的小伙伴那就给我点个👍,是对我最大的支持。嘻嘻~
如有更好的方案,加v共同探讨~
思考分析优化什么
用户吐槽视图呈现慢
。随着数据量的增大,接口返回数据的速度也会变慢,以此同时页面渲染的速度也会变慢。现在需要从前端的角度去思考如何提升渲染速度,使得页面更快的呈现给用户。
我先从页面呈现的元素来分析。为了使得首次加载的速度更快,我将图片通过懒加载
方式去加载。
再者,因为是树状结构的数据,所以我设计了一个递归组件。但随着数据量的增大,递归组件的渲染慢
这个痛点也暴露出来了。所以解决递归组件的渲染问题就会提升首次加载的速度。
采取什么方案去优化
根据上述的分析,我不再直接使用img
标签去加载图片,而是通过第三方库lozad
写了一个懒加载组件PMLazyImg
来加载图片。
实现懒加载组件的大致思路
- 安装
npm install --save lozad
- 在
PMLazyImg
使用 给懒加载元素新增ref
属性 & 通过给元素新增data-src
属性,data-src
属性值fixedSrc
是真实的src路径
<img :data-src="fixedSrc" @click="evt => $emit('click', evt)" alt ref="img" />
实例化lozad
lazyObj.value= lozad();
lazyObj.value.observe();
监听外部传来的src
并将data-src
属性值给元素的src
属性
img.value.dataset['loaded'] = false;
lazyObj.value.triggerLoad(img.value);
到这里懒加载组件基本上完成。
这次我也做了单元格的代码重构,我将单元格的内容抽离成一个组件,这样外部只需要通过配置就可设计自己心仪的单元格内容。那如何通过配置去设计单元格呢?
这里我通过ts
语法进行单元格配置规则。并且每个单元格又形成了一个table-column
配置规则。
现阶段单元格所支持type:单元格类型
、name:表头名称
、prop:单元格字段
、width:单元格宽
、height:单元格高度
等...
具体的代码如下:
/*** 单元格的类型*/
export enum eleType {TEXT}
/*** 操作类型*/
export enum ActionsType { DEL}
interface BtnsType {
label?: String;
props?: Object;
type: ActionsType;
handler: (...args: any) => void;
formatter?: (val?: any, row?: any) => void;
isHide?: (val?: any, row?: any) => void;
}
type BtnsTypeOptions = BtnsType[];
type BtnsConfig = BtnsTypeOptions;
interface DropDownType {
label?: String;
props?: Object;
menu: BtnsConfig;
}
export interface CellTypes {
type: eleType;
name: string;
prop: string | null;
width?: number;
height?: number;
formatter?: (val: any, row: any) => void;
btns?: BtnsConfig;
dropdown?: DropDownType;
handler?: (...args: any) => void;
}
export type ColumnOptions = CellTypes[];
那么下次有新的单元格内容需求变化时,只需要更改配置并且定制新的单元格内容即可。
至于如何解决上述递归组件渲染的痛点,我通过如下方式去优化:
子孙节点如何渲染
- 在模板上我通过
v-if
去控制子节点
与孙节点
是否展示,这样第一次渲染时就不会加载子节点
和孙节点
了,而是在特定情况下去渲染,比如我展开的时候才会去渲染。
子级元素不加载的结构 vs 子级元素加载的结构:
这样也会提升首次加载速度。(之前是通过使用v-show
是控制)
父节点如何渲染
- 我利用
IntersectionObserver
这个api进行节点的懒加载,从而影响递归组件的渲染。
这里要思考一个关键
问题:如何确定数据列表和可见区域的关系?
我在模板上给每个节点绑定了一个id
属性,那么在IntersectionObserver
回调函数里,我可以将那些可见的目标target
元素放入我维护的一个renderList
数组中。再者renderList
数组可以用在递归组件模板渲染每个node
节点时通过节点数据item
的id属性去匹配。最后,观察完的元素要停止观察unobserve
。
在表格布局上,我也进行了部分重构。需要给tbody
新增高度和overflow:auto
属性。
到这里基本上完成了一级节点的渲染。
模板部分代码如下:
也就是在第一次加载时,视图层不可见元素并没有加载
不可见节点 vs 可见节点:
节点复用
通过给每个单元格以及每行数据通过设置key
属性,不再是使用索引进行关联,而是使用每个节点的id
进行关联。
对比优化前后的结果
优化前后性能图:
对比前后的数据
-
完成渲染的时间总计:9336ms vs 4331ms。
-
LCP
:时间:5330.8ms vs 2296.5ms。
注:LCP
Largest Contentful Paint
是一个以用户为中心的性能指标,可以测试用户感知到的页面加载速度,因为当页面主要内容可能加载完成的时候,它记录下了这个时间点。一个快速的LCP,可以让用户感受到这个页面的可用性.
FCP
: 2957.6 ms vs 2296.5ms
注:FCP
全称 First Contentful Paint
,翻译为首次内容绘制
,顾名思义,它代表浏览器第一次向屏幕绘内容
。
思考后续 & 感受
上述优化是基于前端的角度,其实还可以通过接口懒加载的方式去优化。(数据不需要全量返回,先是返回一级元素,等需要了再去调用二级或者三级的接口)当然还会往更好的方式再去优化啦!!
这次代码的重构不仅仅是技术上的优化,我还萌生了无关于技术的一些其他的想法。
开发人员大部分对于频繁的需求变动以及代码重构都会有些抵触和抱怨。(为什么一开始产品设计不先设计好呢😭)
在我看来,关于业务不可能是一层不变的,会随着市场的变化而变化。而作为开发人员应该有着拥抱变化
和积极重构代码
的心态来工作!!
可能改变心态也许就会少了抱怨多了快乐~何乐而不为呢?