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

10 分钟上手 ECharts:从“能跑”到“生产级”的完整踩坑之旅

10 分钟上手 ECharts:从“能跑”到“生产级”的完整踩坑笔记

如果你也曾 复制了官方 Demo 却不知道怎么拆窗口一拉伸图表就变形切换标签页后内存暴涨——这篇博客就是为你写的。
我会用 6 个递进版本 的源码,带你把一张 最简柱状图 逐步进化成 可销毁、可重建、零泄漏 的响应式组件,顺便把 ECharts 的核心 API 一次性讲透。


这里先附上 echart官网

请先耐心阅读文章,文章末附上的有完整源码

00 前言:为什么又写一篇 ECharts 入门?

ECharts 的官方例子足够漂亮,但大多数教程只停在 “hello world” 级别:

echarts.init(dom).setOption(option);

然而真实业务里,我们至少要回答三个问题:

  1. 窗口拉伸怎么办?
  2. 弹窗/标签页切换后图表不见了,再打开为何一片空白?
  3. 反复进出页面,内存为何节节攀升?

今天用 不到 120 行代码 把这三个坑填平,让你 copy-paste 即可投产


01 最小可运行版本(Step-1)——先跑起来再说

文件:step1-hello.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step1</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><script>// 1. 描述你要画什么const option = {title: { text: 'First ECharts' },tooltip: {},legend: { data: ['销量'] },xAxis: { data: ['衬衫','羊毛衫','裤子','袜子','高跟鞋'] },yAxis: {},series: [{ name: '销量', type: 'bar', data: [5,20,36,10,10] }]};// 2.  init → setOption 两行经典 APIconst myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);</script>
</body>
</html>

initsetOption 是 ECharts 的“开机键”和“遥控器”,一句话就能记住:

init 用于创建图表实例,并指定渲染所需的 DOM 节点;
setOption 用于向该实例传入配置项,以生成并更新图表。
必须先执行 init 获得实例,再调用 setOption,否则无法渲染。

此时打开浏览器,粉色区域出现柱状图——任务完成,但别急着提交代码,因为拉伸窗口图表不会跟着变


02 让图表“长”在窗口上(Step-2)——响应式 101

ECharts 暴露的唯一武器是:resize()
我们只需在窗口尺寸变化时调用它。

关键细节addEventListenerremoveEventListener 必须指向同一个函数引用,否则解绑失败 → 内存泄漏。

文件:step2-resize.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step2</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><script>const option = {title: { text: 'First ECharts' },tooltip: {},legend: { data: ['销量'] },xAxis: { data: ['衬衫','羊毛衫','裤子','袜子','高跟鞋'] },yAxis: {},series: [{ name: '销量', type: 'bar', data: [5,20,36,10,10] }]};const myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);/* 统一句柄:后面销毁时还要用 */const handleResize = () => myChart && myChart.resize();window.addEventListener('resize', handleResize);</script>
</body>
</html>

为什么这个匿名函数要这样写?
const handleResize = () => myChart && myChart.resize();

JavaScript 的 && 运算符具备短路特性:左侧表达式为真时,才继续执行右侧;左侧为假时,整个表达式立即返回假,右侧代码不会被执行

resize 场景下,左侧的 myChart 若因销毁而变为 null,右侧的 resize() 调用就会被自动跳过,从而避免空指针错误,实现**一行代码完成“存在判断 + 方法调用”**的防御式逻辑。

现在拉伸窗口,柱子实时重排,响应式闭环达成


03 弹窗关闭 ≠ 直接 remove DOM(Step-3)——销毁实例

场景

  • 标签页切换、弹窗关闭、路由跳转 → 容器节点被移除。
  • 用户再次打开弹窗,发现图表区域空白,控制台报 Cannot read properties of null

原因
dispose() 没调用,ECharts 实例还在旧 DOM 碎片里,内存没释放节点已不存在

官方原话

“在容器节点被销毁时,总是应调用 echartsInstance.dispose 以销毁实例释放资源,避免内存泄漏。”

文件:step3-dispose.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step3</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><button id="ctrl">销毁</button><script>const option = { ... };   // 同上const myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);const handleResize = () => myChart && myChart.resize();window.addEventListener('resize', handleResize);/* 释放内存的逻辑 */const destroyChart = () => {if (myChart) {myChart.dispose();   // 释放 WebGL/Canvas 资源myChart = null;      // 告诉垃圾回收器“我清空了”window.removeEventListener('resize', handleResize);}};document.getElementById('ctrl').addEventListener('click', destroyChart);</script>
</body>
</html>

最佳实践
谁先删 DOM,谁负责 dispose;Vue/React 在 beforeUnmountuseEffect cleanup 里统一销毁。


04 一键“销毁/重建”开关(Step-4)——完整切换逻辑

把销毁/创建封装成两个纯函数,再用按钮模拟“标签页切换”:

文件:step4-toggle.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step4</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><button id="ctrl">销毁</button><script>const option = { ... };   // 同上let myChart = null;let exist = true;                       // 当前是否存在const btn = document.getElementById('ctrl');const createChart = () => {myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);window.addEventListener('resize', handleResize);};const destroyChart = () => {if (myChart) {myChart.dispose();myChart = null;window.removeEventListener('resize', handleResize);}};btn.addEventListener('click', () => {exist ? destroyChart() : createChart();exist = !exist;btn.innerText = exist ? '销毁' : '创建';});createChart();   // 首次自动创建</script>
</body>
</html>
  • 第一次点击 → 销毁(按钮文字变“创建”)
  • 第二次点击 → 重建(按钮文字变“销毁”)

内存监控:Chrome DevTools → Memory → Heap snapshot,反复切换,节点数不再上涨


05 算法级优化(Step-Final)——终身只绑一次 resize

大体写完了,但细节和性能上我们还可以进行优化,比如通过引入三元运算符或者封装函数等方式来优化性能

问题
每次重建都 addEventListener → 理论上会重复绑定同一类型事件(虽然浏览器会去重,但仍不优雅)。

思路
resize 监听与图表生命周期脱钩,只要全局存在一次即可;内部用“懒调度”判断实例是否存在。

文件:step-final.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step-Final</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><button id="ctrl">销毁</button><script>const option = { ... };   // 同上let myChart = null;let exist = true;const btn = document.getElementById('ctrl');/* 终身只绑一次 resize,无论有多少图表 */window.addEventListener('resize', () => myChart && myChart.resize());const createChart = () => {myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);};const destroyChart = () => {myChart && myChart.dispose();myChart = null;};btn.addEventListener('click', () => {exist ? destroyChart() : createChart(); // 表格存在就执行销毁,不存在就执行创建exist = !exist;btn.innerText = exist ? '销毁' : '创建';});createChart();   // 首次自动创建</script>
</body>
</html>

复杂度从 O(绑+解)×NO(1)多图表、路由切换、弹窗堆叠场景下同样适用。


06 直接投产:最终 120 行模板(up主写的完整原生前端三剑客代码)

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>echart practice</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script></head><style>#main,html,body {width: 100%;}#main {height: 400px;}
</style><body><!-- 准备的一个定义好宽高背景色的DOM容器 --><div id="main" style="background-color: pink;"></div><button id="ctrl">销毁</button><!-- 为echart初始化实例 --><script type="text/javascript">// type后面那一坨都是老版html需要写的,用于指定脚本语言,现在HTML5可以省略了// TODO.1 相关变量配置// 表格let myChart = null// 配置项let option = {// 标题组件title: {text: 'First Echart Practice', // 主标题文本subtext: '副标题', // 副标题// left & right & center 用来控制水平位置// top 用于控制垂直位置},  // 提示框组件()鼠标悬停时弹出tooltip: {trigger: 'item',            // 触发方式:'item'(单点) | 'axis'(坐标轴) | 'none'// formatter: '{b}<br/>{a}: {c}' // 自定义浮层内容,模板或回调函数},// 图例组件(点击可控制系列显隐)legend: {data: ['销量'],             // 必须与 series[i].name 保持一致,才能对应// orient: 'horizontal',     // 排列方向:'horizontal'|'vertical'// left: 'right',            // 位置,同 title},// X 轴xAxis: {data: ['衬衫', '羊毛衫', '裤子', '袜子', '高跟鞋']},// Y 轴yAxis: {},// 系列列表(真正决定“画什么图”)series: [{name: '销量',             // 与 legend.data 对应,悬停提示也会用 type: 'bar', data: [5, 20, 36, 10, 10,20]}],}// TODO.2 监听页面大小变化事件const handleResize = () => myChart && myChart.resize() // 防御式写法,如果myChart已被销毁就短路返回,不会执行 resize();如果myChart存在,正常调resize()让图表随窗口大小重绘。// TODO.3 表格创建初始化函数const createChart = () => {myChart = echarts.init(document.getElementById('main'))console.log('表格对象实例化完成')myChart.setOption(option)console.log('表格对象展示完成')window.addEventListener('resize', handleResize) // 浏览器原生事件,当窗口(window)大小发生变化 时触发console.log('表格已建立')}// TODO.4 销毁实例const destroyChart = () => {myChart.dispose();   // 释放内存myChart = null;      // 垃圾回收,将变量制空window.removeEventListener('resize', handleResize);console.log('图表已销毁');}createChart()// TODO.5 销毁和创建实例let ctrlFactor = true // 为true时表格存在const btn = document.getElementById('ctrl')console.log('初始化创建成功')btn.addEventListener('click', () => {btn.innerText = ctrlFactor ? '创建' : '销毁' // 通过控制因子判断按钮文字内容ctrlFactor ? destroyChart() : createChart() // 通过控制因子去判断表格操作ctrlFactor = !ctrlFactor})</script></body>
</html>

复制→保存→打开浏览器,你就拥有了一个:

  • 响应式
  • 可销毁/重建
  • 零内存泄漏

的 ECharts 基准模板,后续只需替换 option 即可快速出图!


07 结语:把模板塞进你的脚手架

  • Vue:在 onMounted 调用 createChartonUnmounted 调用 destroyChart
  • React:在 useEffect(() => { createChart(); return destroyChart; }, []); 即可。
  • 多图表:把 myChart 换成数组或 Map,resize 监听仍只需一次。

至此,内存泄漏、响应式、销毁重建 三大痛点全部解决;
剩下的,就是去 ECharts 官方示例 里复制更炫的 option 了!

Happy charting! 🎉


如果有任何疑问,欢迎在评论区留言讨论!


文章转载自:

http://pVtvMA1x.wynqg.cn
http://0ABbyUoD.wynqg.cn
http://0PgEaYm9.wynqg.cn
http://mCl7XR4V.wynqg.cn
http://TdQc5Ktb.wynqg.cn
http://2ernm3Xj.wynqg.cn
http://1OTLf9mb.wynqg.cn
http://Sw9c0eta.wynqg.cn
http://Aj8fsNG2.wynqg.cn
http://ktaOvjxW.wynqg.cn
http://r6VdGF6F.wynqg.cn
http://K1KCiN1b.wynqg.cn
http://kAvXqDJe.wynqg.cn
http://wBnAsmue.wynqg.cn
http://GkAKfsWH.wynqg.cn
http://avCGo9S8.wynqg.cn
http://YJhYLAtE.wynqg.cn
http://cHDVF0uH.wynqg.cn
http://RlyfpDXa.wynqg.cn
http://caUOkDjh.wynqg.cn
http://uNvusTMZ.wynqg.cn
http://aNv9PhdO.wynqg.cn
http://UXXde19I.wynqg.cn
http://9Es1unyN.wynqg.cn
http://SywCP7ij.wynqg.cn
http://ISQ2ecvq.wynqg.cn
http://c7n5pGhB.wynqg.cn
http://CJT5pkB0.wynqg.cn
http://td2G3j4C.wynqg.cn
http://9g58O8m2.wynqg.cn
http://www.dtcms.com/a/377456.html

相关文章:

  • Decode Global新官网上线披露核心数据
  • 【Redis】Scan 命令使用教程:高效遍历海量数据
  • 深度解析:抗辐射电源芯片 ASP4644S2B 在空间环境中的单粒子效应表现
  • 多链冷换仓攻略:如何在 Solana、BSC 与 Base 隐身管理资产
  • 【博弈论和SG函数 | 那忘算10】巴什博奕 尼姆博弈及其变种 威佐夫博弈(附例题)
  • Cubemx+Fatfs(解决挂载Fatfs失败的问题)
  • UVa1063/LA3807 Marble Game
  • leetcode LCR 170.交易逆序对的总数
  • 【学习笔记】Https证书如何升级到国密
  • 期权行权期限一般多久?
  • 0基础Java学习过程记录——枚举、注解
  • 【C++】C++ 内存管理
  • C++ STL之list的使用
  • Midjourney绘画创作入门操作创作(宣传创意)
  • 【数据库约束】
  • 小白成长之路-centos7部署ceph存储
  • python学习进阶之面向对象(二)
  • 【芯片设计-信号完整性 SI 学习 1.1.1 -- Unit Interval,比特周期】
  • sudo apt update sudo apt upgrade -y 两个命令的作用
  • 每日算法刷题Day68:9.10:leetcode 最短路6道题,用时2h30min
  • apache详细讲解(apache介绍+apache配置实验+apache实现https网站)
  • 一些常用的激活函数及绘图
  • 第3节-使用表格数据-数据库设计
  • 同步时钟系统在体育场游泳馆的应用
  • QT里获取UUID当做唯一文件名称
  • 【Python】pytorch数据操作
  • iOS应用启动深度解析:dyld动态链接器的工作机制与优化实践
  • [硬件电路-175]:multisim中如何给让光电二极管产生光电流?
  • 小巧精准,安全无忧:安科瑞ADL200N-CT/D16-WF防逆流电表守护阳台光伏
  • NLP(自然语言处理, Natural Language Processing)