2年前写了这篇关于在 Vue2 中封装 Echarts 的文章。时过境迁,技术栈已过时,现在按照在 Vue3 中封装 Echarts 的思路和流程,重新整理一下当年的水文,看看 Vue2 的 Options API 在封装上有哪些差异。
和 Vue3 中相同的步骤以及问题,这里就不再赘述,大家可以直接翻阅 Vue3 的那篇文章,链接放在最后了。
技术栈及版本号
技术栈方面选用的还是:Vue2 + Typescript + Echarts,是不是有点旧瓶装新酒的感觉,哈哈哈~
部分依赖的版本号如下:
{
"dependencies": {
"@types/lodash-es": "^4.17.7",
"echarts": "^5.4.2",
"lodash-es": "^4.17.21",
"vue": "^2.6.14",
},
"devDependencies": {
"typescript": "~4.5.5",
}
}
按需导入的配置文件
需要哪些 ECharts 组件就按需导入哪些,可以直接 copy 我的,然后按需修改:👉 config 配置文件
当然,你也可以直接参考 按需导入 和 在 Typescript 中按需引入。
组件容器与实例化
先搭一个基本的架子:
<script lang="ts">
import Vue, { PropType } from 'vue';
import { type EChartsType } from 'echarts/core';
import echarts, { type ECOption } from './config';
export default Vue.extend({
props: {
option: {
type: Object as PropType<ECOption>,
required: true,
default: () => ({} as ECOption),
},
width: {
type: String,
required: true,
},
height: {
type: String,
required: true,
},
theme: {
type: [String, Object],
default: null,
},
},
data() {
return {
chartInstance: undefined as EChartsType | undefined,
};
},
computed: {
chartRef(): HTMLDivElement {
return this.$refs.chart as HTMLDivElement;
},
},
watch: {
option() {
this.draw();
},
},
methods: {
draw() {
this.chartInstance?.setOption(this.option, { notMerge: true });
},
init() {
if (!this.$refs.chart) return;
// 校验 Dom 节点上是否已经挂载了 ECharts 实例,只有未挂载时才初始化
this.chartInstance = echarts.getInstanceByDom(this.chartRef);
if (!this.chartInstance) {
this.chartInstance = echarts.init(this.chartRef, this.theme, {
renderer: 'canvas',
});
}
this.draw();
},
},
mounted() {
this.init();
},
beforeDestroy() {
this.chartInstance?.dispose();
},
});
</script>
<template>
<div ref="chart" class="chart" :style="{ width, height }" />
</template>
chartRef: 当前的 DOM 节点,即 ECharts 的容器;
chartInstance:当前 DOM 节点挂载的 ECharts 实例,可用于调用实例上的方法,注册事件,自适应等;
draw:用于绘制 ECharts 图表,本质是调用实例的 setOption 方法;
init:初始化,在此获取 DOM 节点,挂载实例,注册事件,并调用 draw 绘制图表。
接下来,我们要一步步给架子添加常用的功能。
窗口自适应防抖
在 data 中准备一个自适应函数:
data() {
return {
resize: null as EventListenerOrEventListenerObject | null,
}
}
在 methods 中添加一个用以生成具有防抖优化的自适应函数的工厂函数
import { debounce } from 'lodash-es';
debounceResize() {
return debounce(() => {
this.chartInstance?.resize({
animation: { duration: 300 },
});
}, 500);
},
请注意,这里的 debounceResize 函数本身并不具备自适应功能,它只是给 ECharts 的 resize 函数包裹了一层防抖,并将包装后的函数返回了出来。
所以我们才在 data 里准备了一个 resize 变量,并在组件 created 的时候进行接收:
created() {
this.resize = this.debounceResize();
}
好了,这时我们再去注册 resize 事件,自适应就能生效了:
mounted() {
// 略...
this.resize && window.addEventListener('resize', this.resize);
},
beforeDestroy() {
// 略...
this.resize && window.removeEventListener('resize', this.resize);
},
绑定事件
在初始化时,添加需要绑定的事件:
init() {
// 略......
if (!this.chartInstance) {
// 略......
// 绑定 ECharts 事件
if (typeof this.onMouseover === 'function') {
this.chartInstance.on('mouseover', (event: Object) => {
this.onMouseover(
event,
this.chartInstance,
this.option
);
});
}
// 需要哪些, 添加哪些
}
this.draw();
},
完整代码
<script lang="ts">
import Vue, { PropType } from 'vue';
import { type EChartsType } from 'echarts/core';
import { debounce } from 'lodash-es';
import echarts, { type ECOption } from './config';
export default Vue.extend({
props: {
option: {
type: Object as PropType<ECOption>,
required: true,
default: () => ({} as ECOption),
},
width: {
type: String,
required: true,
},
height: {
type: String,
required: true,
},
theme: {
type: [String, Object],
default: null,
},
loading: {
type: Boolean,
default: false,
},
onMouseover: {
type: Function as PropType<(...args: any[]) => any>,
},
onMouseout: {
type: Function as PropType<(...args: any[]) => any>,
},
},
data() {
return {
chartInstance: undefined as EChartsType | undefined,
resize: null as EventListenerOrEventListenerObject | null,
};
},
computed: {
chartRef(): HTMLDivElement {
return this.$refs.chart as HTMLDivElement;
},
},
watch: {
option() {
this.draw();
},
loading(loading) {
if (this.chartInstance) {
loading
? this.chartInstance.showLoading()
: this.chartInstance.hideLoading();
}
},
},
methods: {
draw() {
this.chartInstance?.setOption(this.option, { notMerge: true });
},
init() {
if (!this.$refs.chart) return;
// 校验 Dom 节点上是否已经挂载了 ECharts 实例,只有未挂载时才初始化
this.chartInstance = echarts.getInstanceByDom(this.chartRef);
if (!this.chartInstance) {
this.chartInstance = echarts.init(this.chartRef, this.theme, {
renderer: 'canvas',
});
// 绑定 ECharts 事件
if (typeof this.onMouseover === 'function') {
this.chartInstance.on('mouseover', (event: Object) => {
this.onMouseover(
event,
this.chartInstance,
this.option
);
});
}
if (typeof this.onMouseout === 'function') {
this.chartInstance.on('mouseout', (event: Object) => {
this.onMouseout(event, this.chartInstance, this.option);
});
}
}
this.draw();
},
debounceResize() {
return debounce(() => {
this.chartInstance?.resize({
animation: { duration: 300 },
});
}, 500);
},
resetChart() {
if (this.chartInstance) {
this.chartInstance.clear();
this.draw();
}
},
},
created() {
this.resize = this.debounceResize();
},
mounted() {
this.init();
this.resize && window.addEventListener('resize', this.resize);
},
beforeDestroy() {
this.chartInstance?.dispose();
this.resize && window.removeEventListener('resize', this.resize);
},
});
</script>
<template>
<div ref="chart" class="chart" :style="{ width, height }" />
</template>