前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >图表列表性能优化:可视化区域内最小资源消耗

图表列表性能优化:可视化区域内最小资源消耗

原创
作者头像
周陆军
发布2021-06-26 17:23:30
2.2K0
发布2021-06-26 17:23:30
举报
文章被收录于专栏:前端架构前端架构

之前写过《懒加载优化:JavaScript IntersectionObserver API监听元素是否可见》,基于上一篇文章,做个滚动懒加载完全不是问题。

但是,如果页面定时自动刷新,不可见区域内的刷新完全是浪费前后端的资源。

本篇在上篇的基础,通过自己的一个改版案例,来看IntersectionObserver+ResizeObserver+getBoundingClientReact+Object.freeze是如何提升项目的整体性能与用户体验的

案例如下:

WeChatWorkScreenshot_e685b007-6459-4c46-974a-b5c114cdfba4.png
WeChatWorkScreenshot_e685b007-6459-4c46-974a-b5c114cdfba4.png

这个页面,不只是简单的滚动加载那么加载。图表也比较复杂

  • 刷新页面操作:切换右侧目录列表、搜索确定、查询搜索、面板手动刷新、面板设置定时自动刷新
  • 刷新图表事项:父子图、关联图、组合图(图表套图表)
  • 尺寸调整事项:浏览器页面尺寸调整、侧边栏收起、侧边栏尺寸拖曳调整,编辑模式下:分组尺寸调整、图表尺寸调整

这个页面之前的实现的挺复杂,而且时不时的报bugger(代码复杂了,出问题的概率肯定会加大)。

来看看你的项目存是否也可能存在以下几个致命问题:

  1. 多图表的列表,多用户设置定时自动刷新,服务器请求特别多,资源消耗严重(如果限制视窗内刷新,十屏滚动,资源就是减少90%)
  2. 图表列表数据过大时,页面卡死,甚至崩溃(
    1. BUS、echarts事件组件注销时没有解绑——函数多次重复执行
    2. 图表数据Vue 深度watch——大数据图表,CPU、内存爆棚,页面直接崩溃
  3. 页面整体事件响应慢——父容器不断遍历通知子组件,性能消耗。
  4. echarts图表刷新慢——很多时候echarts实例重建,而不是调用原来的实例?setOption?
  5. 定时刷新时间不精准,内存泄露——setInterval直接设置定时刷新
  6. windows全局手动管理echarts实例,项目内存占用巨大,甚至内存泄露,页面崩溃

直接开干版

容器滚动,通知容器内组件,需要重新渲染;组内再调用组件内刷新。

同理,当父容器尺寸变化时;或者编辑列表,尺寸调整时;做同样的操作。

这个就是原来的实现方式

面板页面组件:贴出来肯定是简化版的,实际业务复杂得多得多……

代码语言:javascript
复制
<template>
??<div?class="list-box">
????<Group
??????v-for="group?in?list"
??????:key="group.id"
??????ref="group"
????/>
??</div>
</template>
<script>
export?default?{
??watch:?{
????isSidebarOpen()?{
??????this.handleRenderDebounce();
????},
??},
??mounted()?{
????//?debounce?优化性能后,还是会冗余渲染
????this.handleRenderDebounce?=?tools.debounce(this.handleRender,?200);
????//?父容器监听?滚动事件,触发渲染函数
????this.$refs.listBox.addEventListener('scroll',?this.handleRenderDebounce);
????//?页面尺寸调整时,触发滚动函数
????window.onresize?=?this.handleRenderDebounce;
????
????//?重置条件/搜索
????Bus.$on('reloadGroupChart',?()?=>?{
??????if?(this.$refs.group)?{
????????this.$refs.group.forEach((group)?=>?{
??????????if?(group.resetLoadMap)?{
????????????group.resetLoadMap(true);
??????????}
????????});
????????this.$nextTick(()?=>?{
??????????this.handleRender();
????????});
??????}
????});
????//?新建分组,滚动到新分组位置
????Bus.$on('eventScrollToNewGroup',?()?=>?{
??????if?(this.$refs.group?&&?this.$refs.group.length?>?0)?{
????????this.$refs.group[this.$refs.group.length?-?1].$el.scrollIntoView();
??????}
????});
??},
??methods:?{
????handleRender()?{
??????//?每个分组遍历?调用其?渲染函数
??????this.$refs.group.forEach((el)?=>?{
????????el.handleRender();
??????});
????},
??},
};
</script>

<style?lang="scss">

</style>

然后再每个图表组件,再次感觉被轮奸一遍

代码语言:javascript
复制
<template>
??<div?class="list-box">
????<chart-item
??????v-for="chart?of?group.charts"
??????ref="chart"
??????:key="chart.id"
????/>
??</div>
</template>

<script>
export?default?{
??mounted()?{
????Bus.$on('eventRefreshCharts',?()?=>?{
??????for?(const?chart?of?this.$refs.chart?||?[])?{
????????chart.initChartItem();
??????}
????});
????Bus.$on('eventRefreshTargetChart',?(chartId)?=>?{
??????this.$forceUpdate();
??????this.$nextTick(()?=>?{
????????for?(const?chart?of?this.$refs.chart?||?[])?{
??????????if?(chart.chart.id?===?chartId)?{
????????????chart.clickRefreshChart();
??????????}
????????}
??????});
????});
????Bus.$on('eventRefreshCharts',?(_)?=>?{
??????for?(const?chart?of?this.$refs.chart?||?[])?{
????????chart.initChartItem()
??????}
????})
????if?(this.group.charts)?{
??????this.group.charts.forEach((chart)?=>?{
????????this.$set(this.reloadMap,?chart.id,?false);
??????});
????}
????this.$nextTick(()?=>?{
??????//?规避?-?初始化时chart大小位置计算不准确的问题
??????setTimeout(()?=>?{
????????this.handleRender();
??????},?300);
????});
??},
??methods:?{
????//?触发chart加载或更新
????handleRender()?{
??????if?(this.$refs.chart)?{
????????this.$refs.chart.forEach((chart)?=>?{
??????????if?(chart.handleRepeater)?{
????????????chart.handleRepeater();
??????????}
????????});
??????}
????},
????//?重置加载状态
????resetLoadMap(isNeed,?chartId)?{
??????if?(chartId)?{
????????this.reloadMap[chartId]?=?isNeed;
??????}?else?{
????????for?(const?key?in?this.reloadMap)?{
??????????this.reloadMap[key]?=?isNeed;
????????}
??????}
????},
??},
??//?图表展开,重新触发加载
??toggleGroup()?{
????this.group.isExpand?=?!this.group.isExpand;
????this.$nextTick(()?=>?{
??????this.$emit('handleRender');
????});
??},
??//?分组尺寸调整时,……
};
</script>

然后又在每个图表组件里面,去重新渲染子组件。

这个代码就不贴了……

上面代码基本实现了上述的功能,但肯定不符合 高内聚低耦合?的,都俄罗斯套娃了。

自我管理版

先概括地说一下优化思路:

  1. 对于滚动加载,有IntersectionObserver API,滚动时,组件自己判断是否可见,去加载。但是,这里面还要注意下条件
    1. 未初始化时,滚动时候,直接加载就是。并存储当前加载的请求参数,以后后面加载时核验
    2. 已经加载中(组件loading时),无需再加载)
    3. 已经初始化了,需要判断查询条件是否改变,如果改变了,需要再次加载——如查询参数、定时刷新时间
  2. 对于尺寸变化,有ResizeObserver,无论是页面尺寸变化、还是其父组件、爷爷组件尺寸变化,都会反馈到之间本身的尺寸变化,直接监听组件本身就好。
  3. 对于刷新事件,组件自己储备上次加载的参数,接手刷新事件后,自己觉得干啥。
  4. 对于内存CPU+内存爆炸,杜绝图表配置项(option参数)在vue上绑定与监听,可以数据采样;echarts实例、各类绑定事件,及时销毁。

在vue实现上,可以是个公用的基础类,其他图表组件去继承这个类。也可以是一个抽象组件。

下面是算是伪代码级别吧

代码语言:javascript
复制
<template>
??<div?v-bkloading="{isLoading:loading&&uninitialized}"?class="chart-box"?ref="chart">
????<!--图标标题,首次加载,整个图表loading,再次加载,只有标题展示loading?Gif图片-->
????<chart-title
??????:loading="loading"
??????@refreshChart="clickRefreshChart"
????/>
????<!--如果echarts图表封装成组件,不建议通过prop传递option参数(要做也先数据冻结-Object.freeze(option))
????千万不要deep?watch?option,大数据直接奔溃。-->
????<e-chart?ref="eChartRef"?/>
????<!--图表为渲染前,默认占位图-->
????<img?v-else>
??</div>
</template>

<script>
export?default?{
??data()?{
????return?{
??????//?组件未初始化(已经初始化,再次请求,echarts?setOption即可
??????uninitialized:?true,
??????//?数据加载中
??????loading:?true,
??????//?可给定echarts?图表模式示范数据,先展示,等数据请求完毕在展示
??????option:?null,
??????clickRefreshChart:?null,
??????intersectionObserver:?null,
??????resizeObserver:?null,
????};
??},
??beforeDestroy()?{
????//?注意内存泄露问题
????//?this.intersectionObserver.unobserve(this.$el)
????//?建议直接使用?disconnect
????this.intersectionObserver.disconnect();
????this.intersectionObserver?=?null;
????this.resizeObserver.disconnect();
????this.resizeObserver?=?null;
????//?echart?手动释放资源
????if?(this.eChart)?{
??????this.eChart.clear();
??????this.eChart.dispose();
??????this.eChart?=?null;
????}
??},
??mounted()?{
????this.intersectionObserver?=?new?IntersectionObserver((entries)?=>?{
??????if?(entries[0].intersectionRatio?>?0)?{
????????this.initChartItem();
??????}
????});
????this.observeIo.observe(this.$el);
????this.resizeObserver?=?new?ResizeObserver((entries)?=>?{
??????this.BWidth?=?document.getElementById('A').clientWidth?-?20;
????});
????this.resizeObserver.observe(this.$el);
????/*?const?isElementNotInViewport?=?function?(el)?{
??????const?rect?=?el.getBoundingClientRect();
??????return?(
????????rect.top?>=?(window.innerHeight?||?document.documentElement.clientHeight)
????????||?rect.bottom?<=?0
??????);
????};*/
????/**
?????*?刷新图表,值刷新可视化区域内的图标
?????*/
????Bus.$on('clickRefreshChart',?(data)?=>?{
??????if?(!isElementNotInViewport(this.$el))?{
????????this.clickRefreshChart(data);
??????}
????});
????this.clickRefreshChart?=?debounce((data)?=>?{
??????//?TODO?不同的刷新操作,逻辑
??????this.initChartItem(data);
????},?700);
??},
??methods:?{
????//?触发chart加载,更新图标
????async?initChartItem()?{
??????//?在loading中,不重新加载
??????if?(this.loading)?{
????????return;
??????}
??????//?以及初始化,但是查询条件没有刷新,不重新加载
??????if?(!this.uninitialized)?{
????????if?(this.context.start_time?===?this.contextBak.start_time
??????????&&?this.context.end_time?===?this.contextBak.end_time)?{
??????????return?false;
????????}
????????//?TODO?其他条件等等
??????}
??????//?TODO?加载数据,渲染图表
????},
??},
};
</script>

优化,x效果俺是比较满意。

感觉文章写的不是很清楚,但是项目代码是不能直接露的,先这样的吧,后面再补充

欢迎道友们共同探讨,贫道有礼了……

转载本站文章《图表列表性能优化:可视化区域内最小资源消耗》, 请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/html5/2021_0619_8640.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 直接开干版
  • 自我管理版
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com