「性能优化」如何提升递归组件渲染速度

2,495 阅读6分钟

前言

这篇文章是根据公司的业务需求我是这样封装组件关于性能优化的续写。

看一下表格的大致内容:

上次我分享了我是如何根据公司业务封装一个树状结构的多功能表格组件,整体来看能满足前期需求。但随着业务的扩大,随之而来的考验就是性能上能否满足用户。

接下来,我将围绕着性能把自己的所思所想做个记录📝分享出来~

觉得不错的小伙伴那就给我点个👍,是对我最大的支持。嘻嘻~

如有更好的方案,加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 子级元素加载的结构: image.png

image.png

这样也会提升首次加载速度。(之前是通过使用v-show是控制)

父节点如何渲染

  • 我利用IntersectionObserver这个api进行节点的懒加载,从而影响递归组件的渲染。

这里要思考一个关键问题:如何确定数据列表和可见区域的关系?

我在模板上给每个节点绑定了一个id属性,那么在IntersectionObserver回调函数里,我可以将那些可见的目标target元素放入我维护的一个renderList数组中。再者renderList数组可以用在递归组件模板渲染每个node节点时通过节点数据item的id属性去匹配。最后,观察完的元素要停止观察unobserve

在表格布局上,我也进行了部分重构。需要给tbody新增高度和overflow:auto属性。 到这里基本上完成了一级节点的渲染。

模板部分代码如下:

image.png

也就是在第一次加载时,视图层不可见元素并没有加载

不可见节点 vs 可见节点: image.png

image.png

节点复用

通过给每个单元格以及每行数据通过设置key属性,不再是使用索引进行关联,而是使用每个节点的id进行关联。

对比优化前后的结果

优化前后性能图:

image.png

image.png

对比前后的数据

  • 完成渲染的时间总计: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,翻译为首次内容绘制,顾名思义,它代表浏览器第一次向屏幕绘内容

思考后续 & 感受

上述优化是基于前端的角度,其实还可以通过接口懒加载的方式去优化。(数据不需要全量返回,先是返回一级元素,等需要了再去调用二级或者三级的接口)当然还会往更好的方式再去优化啦!!

这次代码的重构不仅仅是技术上的优化,我还萌生了无关于技术的一些其他的想法。

开发人员大部分对于频繁的需求变动以及代码重构都会有些抵触和抱怨。(为什么一开始产品设计不先设计好呢😭)

在我看来,关于业务不可能是一层不变的,会随着市场的变化而变化。而作为开发人员应该有着拥抱变化积极重构代码的心态来工作!!

可能改变心态也许就会少了抱怨多了快乐~何乐而不为呢?