当前位置: 首页 > news >正文

【Vue3】我用 Vue 封装了个 ECharts Hooks

为什么需要这个 Hooks?

先看看我们平时用 ECharts 的常规操作:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

// 常规写法

letchart =null;

// 初始化

onMounted(() => {

constdom = document.getElementById('chart-container');

if(dom) {

chart = echarts.init(dom);

chart.setOption(option);

window.addEventListener('resize', handleResize);

}

});

// 更新数据

constupdateChart = (newData) => {

if(chart) {

chart.setOption({ series: [{ data: newData }] });

}

};

// 处理resize

consthandleResize = () => {

chart?.resize();

};

// 销毁实例

onBeforeUnmount(() => {

window.removeEventListener('resize', handleResize);

chart?.dispose();

});

这段代码不算复杂,但每个图表都要写一遍就很折磨人了。更麻烦的是:

  • 容器获取要处理各种情况(DOM 元素、ID 选择器、Vue Ref)
  • 频繁初始化容易导致内存泄漏
  • 事件绑定 / 解绑需要手动管理
  • 响应式数据更新要手动触发 setOption

useEchart Hooks 来了!

基于以上痛点,我封装了useEchartHooks,核心功能包括:

  • 支持多种容器类型(Ref、DOM 元素、ID / 类选择器)
  • 自动处理初始化与销毁
  • 响应式配置更新
  • 内置事件绑定 / 解绑方法
  • 自动监听窗口 resize

废话不多说先上代码!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

//先导入Echart

import { echarts }from"@/Echarts";

exportinterfaceRefObject {

current?: HTMLElement |null;

}

exportinterfaceCallbackRef {

(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;

} {

letchart: echarts.ECharts |null=null;

letcontainerElement: HTMLElement |null=null;

letresizeObserver: ResizeObserver |null=null;//ResizeObserver 实例

// 辅助函数处理单个选择器

constgetContainerElementForSingle = (

selector:string

): HTMLElement |null=> {

if(selector.startsWith("#")) {

returndocument.getElementById(selector.slice(1)) ||null;

}elseif(selector.startsWith(".")) {

return(document.querySelector(selector)asHTMLElement) ||null;

}

// 直接ID 无#

returndocument.getElementById(selector) ||null;

};

//获取容器元素

constgetContainerElement = (): HTMLElement |null=> {

if(container instanceof HTMLElement) {

returncontainer;

}elseif(typeofcontainer ==="string") {

returngetContainerElementForSingle(container);

}elseif("value"incontainer) {

// Ref 类型

returncontainer.value;

}elseif(Array.isArray(container)) {

// 多个选择器(返回第一个匹配)

for(constselector of container) {

constelement = getContainerElementForSingle(selector);

if(element) {

returnelement;

}

}

}

returnnull;

};

// 初始化图表

constinitChart = ():void=> {

containerElement = getContainerElement();

if(!containerElement) {

console.error("无法获取容器元素");

return;

}

if(!chart) {

chart = echarts.init(containerElement,"infographic");

resizeObserver =newResizeObserver(() => {

chart?.resize();

});

resizeObserver.observe(containerElement);

}

if(option) {

chart.setOption(option);

}

};

// 处理窗口大小变化

consthandleResize = () => {

chart?.resize();

};

// 更新图表配置

constupdate = (newOption: EchartsOption):void=> {

if(chart) {

chart.setOption(newOption);

}

};

// 新增:事件绑定方法

constonChartEvent = (event:string, handler: (params: any) =>void) => {

chart?.on(event, handler);

};

constoffChartEvent = (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{

getchart() {

returnchart;

},

onChartEvent,

offChartEvent,

update,

handleResize

};

}

核心代码解析

先看整体结构,这个 Hooks 主要包含这些部分:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

export function useEchart(container, option) {

letchart =null;

letcontainerElement =null;

// 容器获取逻辑

constgetContainerElement = () => { ... };

// 初始化图表

constinitChart = () => { ... };

// 响应式更新

watch(() => option, (newOption) => { ... });

// 生命周期管理

onMounted(() => initChart());

onBeforeUnmount(() => { ... });

// 暴露API

return{ chart, update, onChartEvent, offChartEvent, handleResize };

}

1. 万能容器处理

最实用的功能之一就是支持多种容器形式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// 支持的容器类型

type container = Ref<HTMLElement |null> | HTMLElement |string|string[];

// 容器获取逻辑

constgetContainerElement = () => {

if(container instanceof HTMLElement) {

returncontainer;

}elseif(typeofcontainer ==="string") {

returngetContainerElementForSingle(container);

}elseif("value"incontainer) {// Vue Ref

returncontainer.value;

}elseif(Array.isArray(container)) {// 多个选择器

for(constselector of container) {

constelement = getContainerElementForSingle(selector);

if(element)returnelement;

}

}

returnnull;

};

无论是直接传 DOM 元素、Vue 的 Ref 对象,还是 ID 选择器(带 #或不带)、类选择器,甚至是选择器数组(自动取第一个匹配项),都能轻松处理。

2. 自动生命周期管理

初始化逻辑会在组件挂载时执行,销毁时自动清理:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// 初始化图表

constinitChart = () => {

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 监听配置变化,自动更新图表:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// 响应式更新图表配置

watch(

() => option,

(newOption) => update(newOption),

{ deep:true}

);

// 事件绑定方法

constonChartEvent = (event:string, handler: (params: any) =>void) => {

chart?.on(event, handler);

};

constoffChartEvent = (event:string, handler: (params: any) =>void) => {

chart?.off(event, handler);

};

如何使用?

用起来超级简单,三步到位:

1. 基础使用

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<template>

<divref="chartRef"class="chart-container"></div>

</template>

<script setup>

import {ref}from'vue';

import { useEchart }from'./useEchart';

// 图表容器

constchartRef =ref(null);

// 初始配置

constoption =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. 事件绑定

1

2

3

4

5

6

// 绑定点击事件

const{ onChartEvent } = useEchart(chartRef, option.value);

onChartEvent('click', (params) => {

console.log('点击了图表',params);

});

3. 动态更新数据

1

2

3

4

5

6

7

8

9

// 直接更新配置

const{ update } = useEchart(chartRef, option.value);

// 按钮点击更新数据

consthandleUpdate = () => {

update({

series: [{ data: [300, 150, 280], type:'line'}]

});

};

为什么这个 Hooks 值得复用?

  1. 减少重复代码:将通用逻辑抽象,每个图表只需关注配置和业务逻辑
  2. 边界处理完善:包含容器不存在、重复初始化等异常情况处理
  3. 灵活性高:支持多种容器形式,适应不同场景
  4. 内存安全:自动清理事件和实例,避免内存泄漏
  5. 响应式友好:完美配合 Vue 的响应式系统,数据变化自动更新图表
http://www.jsqmd.com/news/613435/

相关文章:

  • 紧急!春耕部署窗口仅剩21天:PHP可视化配置模板库(含土壤墒情/气象对接/农机调度3大预置模块)限时开放
  • Java并发编程性能优化终极指南:8个关键点让你的程序运行更快
  • 低查重AI教材写作攻略:工具选择、流程步骤与案例解析
  • 2025最权威的五大降AI率平台解析与推荐
  • Raspberry Pi Imager技术深度解析:跨平台系统部署架构与实战指南
  • nanobot参数详解:Qwen3-4B-Instruct vLLM部署中的max_model_len、tensor_parallel_size设置
  • Doom3.gpl跨平台开发:Windows、Linux、MacOS适配终极指南
  • Leather Dress Collection开源镜像:236MB轻量级LoRA集合支持消费级GPU本地部署
  • 如何用CefFlashBrowser完美解决Flash内容浏览难题:终极Flash浏览器指南
  • 如何快速掌握Notepad--:跨平台文本编辑器的完整实战指南
  • FireRed-OCR Studio快速上手:火红像素风UI下拖拽上传→实时渲染→一键下载
  • 从入门到精通:2026年AI大师30步路线图,覆盖基础、深度学习到项目部署
  • 5个实战技巧:快速掌握新一代AI组件开发
  • 编程竞赛辅助工具终极指南:自动题目解析与算法竞赛高效助手
  • OpenClaw技能市场巡礼:Qwen3.5-9B十大实用自动化模块
  • 2026最权威的降重复率方案推荐
  • 告别阴阳师重复操作:FK-Onmyoji高效自动化解决方案
  • Omaha安全机制详解:证书签名、代码验证与防篡改保护
  • PHP 8.9 JIT在高并发API网关中的真实表现(对比PHP 8.2/8.3:QPS+312%,内存下降38%)
  • 粘度仪供应商盘点:这些厂家值得关注 - 品牌推荐大师
  • 同事当了5年舔狗,leader说啥都说好,从不反驳。今年升了P7,我技术比他强,还在P5,技术不如站队重要吗?
  • 别再踩坑了!SQL Server数据类型那点事儿,看懂这篇少背三个锅唇
  • 小白友好:Qwen2.5-7B镜像LoRA微调教程,附数据集与代码
  • smcFanControl终极指南:让你的Intel Mac告别过热降频,性能全开
  • 基于yoloV8安全帽反光衣检测(含代码)
  • 弦音墨影快速上手:水墨交互界面+自然语言提问+ bounding box 输出全流程
  • Nanbeige4.1-3B Chainlit企业就绪:GDPR数据擦除、会话加密、审计日志留存策略
  • [三指拖拽] Windows Precision触控板手势迁移完全指南:从设备适配到流畅体验
  • 3个步骤解决跨平台应用安装难题:APK Installer的无缝集成方案
  • 无尘投料站哪家做得好?这份攻略帮你选对供应商 - 品牌推荐大师1