- 需求
在PC端开发含图表展示的页面时,需要考虑用户调整浏览器窗口大小后图表能够根据窗口大小自适应变化。
- 常规做法
在含有图表组件mounted的钩子中增加监听window的resize事件,具体如下代码所示:
<template>
<div>
<div id="chart-container"></div>
</div>
</template>
<script>
import echarts from 'echarts'
import options from "..."
export default {
data(){
return {}
},
mounted(){
let self = this
this.echartInstance = echarts.init(document.getElementById('chart-container'));
this.echartInstance.setOption(options)
window.addEventListener("resize",function(){
self.echartInstance.resize()
})
}
}
</script>
<style>
#chart-container{
width: 100%;
height: 100%;
}
</style>
复制代码
然而上述代码却存在内存泄漏的风险:组件被注销时,缩放函数是匿名函数,且仍然在事件监听列表中,因此匿名函数和匿名函数中用到的外部变量在组件注销后均不会被清理。改进方案如下:
<template>
<div>
<div id="chart-container"></div>
</div>
</template>
<script>
import echarts from 'echarts'
import options from "..."
export default {
data(){
return {}
},
mounted(){
this.echartInstance = echarts.init(document.getElementById('chart-container'));
this.echartInstance.setOption(options)
window.addEventListener("resize",this.resizeHandle)
},
methods:{
resizeHandle(){
this.echartInstance.resize()
}
},
destroyed(){
window.removeEventListener("resize",this.resizeHandle)
}
}
</script>
<style>
#chart-container{
width: 100%;
height: 100%;
}
</style>
复制代码
上述代码总算解决了内存泄漏的风险,但是代码比较冗余。 在图表较少时上述方案是可行的,当项目中图表数量较多时,代码便会十分冗余,如果忘了在destroyed中的清理工作还会造成内存泄漏的风险。
- 解决方案
图表随窗口大小自适应调整算是图表组件的一个通用功能,且与具体的图表业务无关,且其逻辑与组件的生命周期精密相关,因此想着将其封装成一个插件,插件代码如下:
/**
* echarts 图表自适应窗口大小变化的指令
* useage: ①main函数中引入:import '@/directive/echartResizeHelper.js'
* ②echarts容器上使用指令 <div id="chart-container" v-on-echart-resize></div>
*/
import echarts from 'echarts'
import Vue from 'vue';
export var version = '0.0.1';
var compatible = (/^2\./).test(Vue.version);
if (!compatible) {
Vue.util.warn('vue echarts resize directive ' + version + ' only supports Vue 2.x, and does not support Vue ' + Vue.version);
}
let HANDLER = "_vue_echarts_resize_handler"
function bind(el, binding){
unbind(el);
el[HANDLER]=function(){
let chart=echarts.getInstanceByDom(el);
if(!chart){
return;
}
chart.resize();
}
window.addEventListener("resize",el[HANDLER])
}
function unbind(el){
window.removeEventListener("resize",el[HANDLER]);
delete el[HANDLER];
}
var directive = {
bind: bind,
unbind: unbind
};
const onEchartResize=Vue.directive("onEchartResize",directive)
export {onEchartResize};
复制代码
代码中主要用到vue自定义指令提供的bind和unbind钩子函数,代码逻辑比较简单,这里就不详细解释了。
使用上述插件,前面的图表组件可写成如下方式:
<template>
<div>
<div id="chart-container" v-on-echart-resize></div>
</div>
</template>
<script>
import echarts from 'echarts'
import options from "..."
export default {
data(){
return {}
},
mounted(){
this.echartInstance = echarts.init(document.getElementById('chart-container'));
this.echartInstance.setOption(options)
}
}
</script>
<style>
#chart-container{
width: 100%;
height: 100%;
}
</style>
复制代码
使用该插件的优点:
①具体业务组件中不需要处理图表缩放功能,代码量较少;
②不用担心组件销毁时忘记清理资源
在移动端图表项目中手机横屏和竖屏切换时可能也存在类似需求,只需将指令中监听的resize事件改为orientationchange事件即可复用。