Vue3项目里怎么正确引入和使用ECharts?有没有避坑指南?
最近身边刚转Vue3的前端朋友全在问ECharts的问题:一会儿说页面跳转回来图表没了,一会儿说自适应效果失效,一会儿更离谱——连基础的CDN引入都报错找不到模块,刚好这段时间我刚重构完一个数据可视化后台,踩了一堆坑又填得差不多,索性把这些实操经验整理成大家能直接抄的问答,从入门配置到进阶优化都覆盖到。
入门必看:Vue3有哪些引入ECharts的方式?
最常用的是三种:CDN引入、npm/yarn/pnpm全量引入、按需引入,不同场景选不同的就行,没必要跟风用最高大上的。
先说说CDN引入,这个适合快速做原型、写小demo或者临时给静态页加个图的情况,比如你刚学Vue3,想先看个效果练手,不用搭复杂的打包工具流程,具体步骤很简单:首先在index.html的head标签里引入Vue3的CDN和ECharts的CDN,ECharts记得选最新的稳定版;然后在Vue的script setup标签里把echarts挂载到window上用,不过注意要写在onMounted生命周期里,因为DOM元素得先渲染出来才能拿到宽高初始化图表,这里有个小细节:如果你的index.html是用Vite生成的,记得在script标签的type设为"module"的情况下,要通过window对象访问非ES模块的CDN资源,直接import会报错,这个很多新手都会踩。
然后是全量npm引入,适合中大型项目里图表类型多、但不用纠结那几兆打包体积(比如企业内网系统、PC端后台这种对首屏加载速度要求不极致的),步骤也不麻烦:先在终端里敲npm install echarts安装依赖;然后在组件里直接import * as echarts from 'echarts',同样要在onMounted里拿到DOM元素初始化,onUnmounted里销毁实例,不然会内存泄漏,全量引入的好处是不用管每个图表用了哪些组件,直接复制ECharts官网的option就能跑,缺点是打包后会多4-5MB的体积,Vite还好有tree-shaking,但webpack旧版本(比如4之前)可能不太好。
按需引入,这是现在主流推荐的,不管大小项目用这个打包体积都会小很多,特别是移动端H5项目,能省不少带宽,按需引入有两种写法:一种是手动按需,一种是用Vite的插件自动按需,手动的话比较灵活但有点麻烦,自动的话省心省力,手动按需的步骤是:先安装echarts,然后引入echarts/core,引入需要的图表类型(比如BarChart、LineChart、PieChart),引入需要的组件(比如TitleComponent、TooltipComponent、LegendComponent、GridComponent、DatasetComponent),最后调用echarts.use()把它们注册进去;自动按需的话,Vite项目可以用vite-plugin-echarts这个插件,先npm install vite-plugin-echarts -D,然后在vite.config.js里配置plugins,之后直接在组件里import * as echarts from 'echarts/core',再单独引需要的图表和组件就行?不对不对,等下自动按需的插件其实是可以帮你解析option自动引入的?哦对,这个插件确实有自动解析的功能,但有时候解析不太准,特别是自定义图表样式的时候,所以建议还是手动注册常用的组件和图表类型,剩下的偶尔用的再临时加,这样既灵活又不容易出错。
踩坑第一弹:页面跳转/组件销毁后图表内存泄漏怎么办?
这个问题我一开始也没注意,后来项目里图表页面开多了,再切回来发现浏览器直接卡成PPT,一看任务管理器内存占用直接飙升到2GB多,后来查了下才知道,ECharts的实例如果在组件销毁的时候不手动清除,它内部的定时器、事件监听器都会一直占用内存,时间久了甚至会导致浏览器崩溃。
那怎么解决呢?其实很简单,只需要记住两点:一是在组件销毁的生命周期里调用echartsInstance.dispose()方法销毁实例;二是如果给图表绑定了resize事件,记得在销毁前把这个事件监听器也移除掉,不然监听器还会一直挂在window上,具体的代码逻辑大概是这样的:先在script setup里用ref定义一个chartInstance变量,初始值设为null;然后在onMounted里拿到DOM元素初始化,赋值给chartInstance;接着如果要加自适应的话,用window.addEventListener绑定resize事件,事件函数里调用chartInstance.resize();最后在onUnmounted里先判断chartInstance有没有值,有的话先window.removeEventListener移除resize事件,再调用chartInstance.dispose()把实例设为null,这里还有个小技巧:如果你用的是Vue3的组合式API,可以把这些初始化、自适应、销毁的逻辑封装成一个自定义Hook,比如useECharts,这样每个图表组件都能直接复用,不用重复写代码,也不容易漏写销毁逻辑。
踩坑第二弹:ECharts在Vue3的响应式数据里更新option后图表不渲染怎么办?
这个问题也是转Vue3的开发者遇到最多的,因为Vue2里直接把option放在data里,修改某个属性就能触发视图更新,但Vue3里情况不一样了,ECharts的实例初始化后,不会自动监听option的变化,你得手动调用echartsInstance.setOption()方法来更新图表;如果你把option直接放在ref或reactive里,修改深层属性的时候可能会触发Vue3的响应式更新,但这个更新对ECharts没用,反而会导致重复渲染;如果option里有数组或对象的引用没有变,只是内容变了,setOption()的第二个参数要设为true,或者使用替换整个option的方式来更新,不然ECharts可能会合并旧的option,导致显示的图表不对。
那正确的更新方式是什么呢?我总结了三种:第一种是替换整个option,这种最简单也最不容易出错,不管修改什么属性,直接重新生成一个新的option对象传给setOption()就行,不过适合数据量不大、option结构简单的情况;第二种是按需修改option的属性,然后调用setOption(),适合数据量大、option结构复杂的情况,不过修改的时候要注意数组或对象的引用,最好用Vue3的响应式工具函数(比如toRaw)先把ref或reactive里的数据转成普通对象,修改后再传进去,不然可能会有性能问题;第三种是使用ECharts的setOption()的第三个参数notMerge,设为true的话会完全替换旧的option,设为false的话会合并,这个参数和第二个参数replaceMerge不一样,replaceMerge是用来指定合并策略的,比如legend的data如果replaceMerge设为['legend'],就会替换旧的legend.data,而不是合并,还有个更重要的点:不要把option放在reactive里,因为Vue3的Proxy代理会对ECharts内部的操作产生干扰,最好放在普通的ref里,或者直接定义一个普通的变量,只在需要的时候调用setOption()更新。
踩坑第三弹:ECharts图表的自适应效果失效怎么办?
自适应效果失效一般有三种原因:一是DOM元素的宽高没有设置好,ECharts的初始化需要一个有明确宽高的DOM元素,不然初始化出来的图表是0x0的;二是resize事件没有正确绑定或移除,刚才内存泄漏的问题里提到过,要是在组件销毁前没移除resize事件,下次再初始化图表的时候可能会绑定多个,导致图表反复resize,影响性能;三是容器的宽高变化是由Vue的响应式数据控制的,但没有监听这个变化调用resize(),比如你用v-if切换图表的显示隐藏,或者用v-model控制容器的宽度,这时候window的resize事件不会触发,得自己监听容器的宽高变化。
先说说第一种原因的解决办法:容器的宽高一定要用CSS明确设置,比如width: 100%; height: 500px; 或者用flex布局,给容器设置flex: 1; 但注意flex布局的话,父元素也要有明确的高度,不然子元素的flex: 1; 是没用的,还有个小细节:如果容器是用v-if渲染的,那得在DOM元素完全渲染出来之后再初始化图表,比如用nextTick包裹onMounted里的初始化代码,或者用watch监听v-if控制的变量,变量变为true的时候再初始化。
第二种原因的解决办法刚才已经讲过了,就是在组件销毁的生命周期里先移除resize事件,再销毁实例,这里还有个优化点:可以给resize事件加个防抖,比如延迟300ms再调用chartInstance.resize(),这样在窗口快速拖动的时候不会频繁触发resize,提升性能。
第三种原因的解决办法:可以用Vue的watchEffect监听容器的宽高变化,或者用ResizeObserver API直接监听容器的尺寸变化,ResizeObserver API现在大部分主流浏览器都支持了,兼容性很好,比watchEffect更准确,因为它只监听容器本身的尺寸变化,不会监听其他无关的响应式数据,具体的代码逻辑大概是这样的:先在onMounted里拿到DOM元素,创建一个ResizeObserver实例,实例的回调函数里调用chartInstance.resize(),然后调用observe()方法监听DOM元素;最后在onUnmounted里先调用disconnect()方法断开ResizeObserver的监听,再销毁实例。
进阶优化:Vue3项目里怎么提升ECharts的渲染性能?
刚才讲的按需引入其实就是一种性能优化,除此之外还有几种常用的方法:一是使用ECharts的渲染模式,ECharts有两种渲染模式:canvas和svg,默认是canvas,适合渲染大数据量的图表(比如折线图有几万条数据),svg适合渲染小数据量、需要缩放不失真的图表(比如仪表盘、地图),可以根据具体的场景选择;二是开启ECharts的大数据量渲染优化,比如给line chart的series设置sampling: 'lttb',这个算法会在数据量很大的时候自动采样,保留折线的趋势,减少渲染的点;三是避免频繁更新option,如果数据更新频率很高(比如实时数据),可以先把数据缓存起来,每隔一段时间(比如1s)再统一更新一次option;四是使用虚拟DOM?不对不对,ECharts本身已经优化过渲染了,不需要用虚拟DOM,反而会增加性能开销;五是复用ECharts的实例**,比如在同一个页面里有多个图表,可以用一个DOM元素通过切换option的方式来复用实例,不过这个适合图表切换不频繁的情况。
哦对了,还有个进阶的操作:如果你的项目里有多个图表组件,可以把ECharts的实例放在provide里,然后在子组件里inject,这样可以避免重复创建实例?不对不对,每个图表需要单独的DOM元素,所以不能共享实例,但是可以共享一些公共的配置,比如theme、locale,这样可以减少重复代码。
要是你用的是ECharts 5.0以上的版本,还可以试试ECharts的dataset组件,用它来管理数据会比直接把数据放在series里更清晰,也更容易做数据的筛选和联动,比如你有一个折线图和一个饼图,数据来源是同一个接口,就可以把数据放在dataset里,然后两个图表分别引用dataset的数据,这样更新数据的时候只需要更新一次dataset就行,不用分别更新两个series。
最后再提一下ECharts的主题,要是你的项目里有统一的UI风格,可以自己定义一个主题,或者用ECharts官网的主题编辑器生成一个主题,然后在初始化图表的时候传入主题名称,这样可以统一所有图表的样式,不用每个图表都写一堆重复的option配置。
Vue3引入和使用ECharts其实不难,只要记住正确的引入方式、生命周期的处理、option的更新方法、自适应的实现,再加上一些性能优化的小技巧,就能做出效果很好的数据可视化图表了,要是还有其他问题,可以留言问我,我会尽量解答的。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



