🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
前言
在前端開發中,ECharts 作為數據可視化的利器被廣泛使用,但每次使用都要重複處理初始化、容器獲取、事件綁定、窗口 resize 等邏輯,不僅繁瑣還容易出錯。最近我封裝了一個
useEchartHooks,徹底解決了這些痛點,今天就來分享一下實現思路和使用技巧。
為什麼需要這個 Hooks?
先看看我們平時用 ECharts 的常規操作:
// 常規寫法
let chart = null;
// 初始化
onMounted(() => {
const dom = document.getElementById('chart-container');
if (dom) {
chart = echarts.init(dom);
chart.setOption(option);
window.addEventListener('resize', handleResize);
}
});
// 更新數據
const updateChart = (newData) => {
if (chart) {
chart.setOption({ series: [{ data: newData }] });
}
};
// 處理resize
const handleResize = () => {
chart?.resize();
};
// 銷燬實例
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
chart?.dispose();
});
這段代碼不算複雜,但每個圖表都要寫一遍就很折磨人了。更麻煩的是:
- 容器獲取要處理各種情況(DOM 元素、ID 選擇器、Vue Ref)
- 頻繁初始化容易導致內存泄漏
- 事件綁定 / 解綁需要手動管理
- 響應式數據更新要手動觸發 setOption
useEchart Hooks 來了!
基於以上痛點,我封裝了useEchart Hooks,核心功能包括:
- 支持多種容器類型(Ref、DOM 元素、ID / 類選擇器)
- 自動處理初始化與銷燬
- 響應式配置更新
- 內置事件綁定 / 解綁方法
- 自動監聽窗口 resize
廢話不多説先上代碼!
//先導入Echart
import { echarts } from "@/Echarts";
export interface RefObject {
current?: HTMLElement | null;
}
export interface CallbackRef {
(el: HTMLElement | null): void;
}
export type EchartsOption = echarts.EChartsOption;
export type container =
| Ref<HTMLElement | null>
| HTMLElement
| string
| string[];
/**
* 適配多種容器選擇方式的 ECharts 封裝
* @param container - 容器選擇器(支持 Ref, DOM 元素, ID 選擇器, 類選擇器)
* @param option - 初始配置
* @returns {chart: echarts.ECharts | null, update: (newOption: EChartsOption) => void}
*/
export function useEchart(
container: container,
option: EchartsOption = {}
): {
chart: echarts.ECharts | null;
onChartEvent: (event: string, handler: (params: any) => void) => void;
offChartEvent: (event: string, handler: (params: any) => void) => void;
update: (newOption: EchartsOption) => void;
handleResize: () => void;
} {
let chart: echarts.ECharts | null = null;
let containerElement: HTMLElement | null = null;
let resizeObserver: ResizeObserver | null = null; //ResizeObserver 實例
// 輔助函數處理單個選擇器
const getContainerElementForSingle = (
selector: string
): HTMLElement | null => {
if (selector.startsWith("#")) {
return document.getElementById(selector.slice(1)) || null;
} else if (selector.startsWith(".")) {
return (document.querySelector(selector) as HTMLElement) || null;
}
// 直接ID 無#
return document.getElementById(selector) || null;
};
//獲取容器元素
const getContainerElement = (): HTMLElement | null => {
if (container instanceof HTMLElement) {
return container;
} else if (typeof container === "string") {
return getContainerElementForSingle(container);
} else if ("value" in container) {
// Ref 類型
return container.value;
} else if (Array.isArray(container)) {
// 多個選擇器(返回第一個匹配)
for (const selector of container) {
const element = getContainerElementForSingle(selector);
if (element) {
return element;
}
}
}
return null;
};
// 初始化圖表
const initChart = (): void => {
containerElement = getContainerElement();
if (!containerElement) {
console.error("無法獲取容器元素");
return;
}
if (!chart) {
chart = echarts.init(containerElement, "infographic");
resizeObserver = new ResizeObserver(() => {
chart?.resize();
});
resizeObserver.observe(containerElement);
}
if (option) {
chart.setOption(option);
}
};
// 處理窗口大小變化
const handleResize = () => {
chart?.resize();
};
// 更新圖表配置
const update = (newOption: EchartsOption): void => {
if (chart) {
chart.setOption(newOption);
}
};
// 新增:事件綁定方法
const onChartEvent = (event: string, handler: (params: any) => void) => {
chart?.on(event, handler);
};
const offChartEvent = (event: string, handler: (params: any) => void) => {
chart?.off(event, handler);
};
// 響應式更新圖表配置
watch(
() => option,
(newOption) => update(newOption),
{
deep: true,
}
);
onMounted(() => {
initChart();
});
onBeforeUnmount(() => {
if (chart) {
// 清理 ResizeObserver 實例
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
chart.dispose();
chart = null;
}
});
return {
get chart() {
return chart;
},
onChartEvent,
offChartEvent,
update,
handleResize
};
}
核心代碼解析
先看整體結構,這個 Hooks 主要包含這些部分:
export function useEchart(container, option) {
let chart = null;
let containerElement = null;
// 容器獲取邏輯
const getContainerElement = () => { ... };
// 初始化圖表
const initChart = () => { ... };
// 響應式更新
watch(() => option, (newOption) => { ... });
// 生命週期管理
onMounted(() => initChart());
onBeforeUnmount(() => { ... });
// 暴露API
return { chart, update, onChartEvent, offChartEvent, handleResize };
}
1. 萬能容器處理
最實用的功能之一就是支持多種容器形式:
// 支持的容器類型
type container = Ref<HTMLElement | null> | HTMLElement | string | string[];
// 容器獲取邏輯
const getContainerElement = () => {
if (container instanceof HTMLElement) {
return container;
} else if (typeof container === "string") {
return getContainerElementForSingle(container);
} else if ("value" in container) { // Vue Ref
return container.value;
} else if (Array.isArray(container)) { // 多個選擇器
for (const selector of container) {
const element = getContainerElementForSingle(selector);
if (element) return element;
}
}
return null;
};
無論是直接傳 DOM 元素、Vue 的 Ref 對象,還是 ID 選擇器(帶 #或不帶)、類選擇器,甚至是選擇器數組(自動取第一個匹配項),都能輕鬆處理。
2. 自動生命週期管理
初始化邏輯會在組件掛載時執行,銷燬時自動清理:
// 初始化圖表
const initChart = () => {
containerElement = getContainerElement();
if (!containerElement) {
console.error("無法獲取容器元素");
return;
}
if (!chart) {
chart = echarts.init(containerElement, "infographic");
chart.resize();
window.addEventListener("resize", handleResize);
}
chart.setOption(option);
};
// 組件卸載時清理
onBeforeUnmount(() => {
if (chart) {
window.removeEventListener("resize", handleResize);
chart.dispose();
chart = null;
}
});
再也不用擔心忘記解綁事件或銷燬實例導致的內存泄漏了!
3. 響應式與事件處理
內置 watch 監聽配置變化,自動更新圖表:
// 響應式更新圖表配置
watch(
() => option,
(newOption) => update(newOption),
{ deep: true }
);
// 事件綁定方法
const onChartEvent = (event: string, handler: (params: any) => void) => {
chart?.on(event, handler);
};
const offChartEvent = (event: string, handler: (params: any) => void) => {
chart?.off(event, handler);
};
如何使用?
用起來超級簡單,三步到位:
1. 基礎使用
<template>
<div ref="chartRef" class="chart-container"></div>
</template>
<script setup>
import { ref } from 'vue';
import { useEchart } from './useEchart';
// 圖表容器
const chartRef = ref(null);
// 初始配置
const option = ref({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'line' }]
});
// 初始化圖表
const { chart, update } = useEchart(chartRef, option.value);
</script>
2. 事件綁定
// 綁定點擊事件
const { onChartEvent } = useEchart(chartRef, option.value);
onChartEvent('click', (params) => {
console.log('點擊了圖表', params);
});
3. 動態更新數據
// 直接更新配置
const { update } = useEchart(chartRef, option.value);
// 按鈕點擊更新數據
const handleUpdate = () => {
update({
series: [{ data: [300, 150, 280], type: 'line' }]
});
};
為什麼這個 Hooks 值得複用?
- 減少重複代碼:將通用邏輯抽象,每個圖表只需關注配置和業務邏輯
- 邊界處理完善:包含容器不存在、重複初始化等異常情況處理
- 靈活性高:支持多種容器形式,適應不同場景
- 內存安全:自動清理事件和實例,避免內存泄漏
- 響應式友好:完美配合 Vue 的響應式系統,數據變化自動更新圖表
最後
這個useEchart Hooks 已經在我們項目中大規模使用,極大提升了開發效率。如果你也經常和 ECharts 打交道,不妨試試這個封裝思路,也可以根據自己的需求擴展更多功能(比如主題切換、加載狀態等)。
完整代碼已經放在開頭,直接複製就能用,有任何優化建議歡迎在評論區交流~