之前写过《懒加载优化:JavaScript IntersectionObserver API监听元素是否可见》,基于上一篇文章,做个滚动懒加载完全不是问题。
但是,如果页面定时自动刷新,不可见区域内的刷新完全是浪费前后端的资源。
本篇在上篇的基础,通过自己的一个改版案例,来看IntersectionObserver+ResizeObserver+getBoundingClientReact+Object.freeze是如何提升项目的整体性能与用户体验的
案例如下:
这个页面,不只是简单的滚动加载那么加载。图表也比较复杂
这个页面之前的实现的挺复杂,而且时不时的报bugger(代码复杂了,出问题的概率肯定会加大)。
来看看你的项目存是否也可能存在以下几个致命问题:
容器滚动,通知容器内组件,需要重新渲染;组内再调用组件内刷新。
同理,当父容器尺寸变化时;或者编辑列表,尺寸调整时;做同样的操作。
这个就是原来的实现方式
面板页面组件:贴出来肯定是简化版的,实际业务复杂得多得多……
<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>
然后再每个图表组件,再次感觉被轮奸一遍
<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>
然后又在每个图表组件里面,去重新渲染子组件。
这个代码就不贴了……
上面代码基本实现了上述的功能,但肯定不符合 高内聚低耦合 的,都俄罗斯套娃了。
先概括地说一下优化思路:
在vue实现上,可以是个公用的基础类,其他图表组件去继承这个类。也可以是一个抽象组件。
下面是算是伪代码级别吧
<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 删除。