记录一次在uni-app中使用echarts的坑
背景
在 uni-app 的内置组件和官方扩展组件中,是没有支持图表的组件的。通过内置组件-画布-canvas
的页面内容,可以找到官方文档对图表使用的解释:
一开始准备直接尝试使用uChart的,看到微信扫一扫就劝退了。反正我只需要基础的图表功能,也不太在乎性能,再加上之前在Vue项目写过echarts代码,对echarts官方的文档说明比较了解,就选择了另一个插件:echarts 。
这个插件主要就是为了让 uni-app 能兼容echarts,所有图表相关语法都是直接用echarts的,所以上手就比较简单些。
开始使用
在插件文档的代码演示中,找到Vue3版本的示例代码,直接拷贝,运行项目,图表正常展示。
现在就是需要自定义自己的图表样式了,我做的图表是一个饼图,也没什么样式要求,整个option的结构定义如下:
1 | { |
所以只需要搞定data数据的异步加载和更新就可以了。
因为我做的功能是要求饼图能根据页面选择的日期加载指定日期的统计数据,所以饼图是需要刷新变化的,按照echarts的说法,可以直接对chart实例调用setOption
方法,将整个option重新赋值就可以了。
但是,事与愿违,无论我怎么尝试,图表始终展示的都是第一次渲染的data数据。期间通过GPT、Google、echarts官方文档、插件文档等多种途径寻找问题根源,以下是我发起的问题:
1 | 以下是我在uni-app项目中,一个vue文件关于echarts的部分代码,在交互触发searchData()导致数据变化后,图表并没有发生变化。 |
GPT的答复中,给出了多方面的检查和调整思路:
- 图表实例的初始化与数据更新时机
myChart
可能未在数据变化时正确初始化。如果 searchData
在图表初始化之前触发,数据将不会被正确渲染。
在 searchData
触发前确保 myChart
已被初始化:
1 | onReady(() => { |
- 数据响应式处理
虽然你使用了 reactive
和 ref
,但是在 Promise.all
中直接更新 categoryDataRef.value.chartList
可能没有触发 watch
。尝试通过 Vue.set
方式强制数据更新:
1 | categoryDataRef.value.chartList = [...categoryData.chartList]; // 确保数据发生变化 |
watch
的深度监听
watch
中使用了 { deep: true }
,但由于 categoryDataRef.value.chartList
是数组,Vue 有时不会捕捉到数组内部的变化。为了确保监控到变化,可以尝试:
1 | watch(() => categoryDataRef.value.chartList.slice(), (newVal, oldVal) => { |
使用 slice()
方法创建新数组,触发 Vue 的响应式系统。
setOption
强制更新
你已经使用了 setOption
的强制更新参数 true
,这一点是正确的,可以保证即使数据小幅更新,图表也会重新渲染。
确保 renderChart
确实在数据变更后被正确调用,检查日志输出:
1 | function renderChart() { |
总结
可以尝试以下步骤:
- 确保
myChart
初始化在searchData
调用之前。 - 通过
slice()
或者使用Vue.set
强制触发watch
的响应式更新。 - 在
renderChart
中检查数据是否正确传递给setOption
。
如果这些调整都没有效果,可以在 renderChart
内部打印日志以进一步调试数据更新的时机与流程。
---- 分段 ----
通过上述思路,我首先在 renderChart
方法中,增加了打印 option 的代码:
1 | const currentOption = myChart.value.getOption(); |
通过打印发现,series的data数据确实没有更新,说明myChart.value.setOption
没有生效。
第1点,图表实例的初始化与数据更新时机,这个其实只要自己代码逻辑清晰,数据更新时机是肯定没问题的,至于实例初始化问题,第一次渲染的数据图表都出来,所以初始化也没有问题。
第2点,认为数据可能没有触发watch
,其实通过日志打印,是可以看到每次数据发生变化后,触发了watch
方法的。之前我没有使用监听方法,而是在每次加载数据后手动调用renderChart
方法,但总是出现一些莫名其妙的问题,于是就改成监听方式了。
第3点,watch
的深度监听,这个跟第2点一样,是可以忽略的,因为打印了日志,说明能监听到数据变化。
然后我根据资料解释,一度怀疑是echarts没有触发更新,于是尝试了echarts的clear
、dispose
、notMerge: true
等方法,直到解决问题后才幡然醒悟,属性数据都没有发生变化,echarts图表肯定不会重新渲染,所以不存在第4点说的问题。
最后,我重整思绪,基本可以确定是myChart.value.setOption
这段代码出现了问题,但是实在是想不通这样有什么不对,因为myChart
确实就是echarts的对象实例啊,而且第一次加载的数据也渲染出来了。
整段代码中,其实都很好理解,因为都是vue或者echarts的语法内容,可以确定没问题,问题的关键还是在这个插件上,chartRef.value.init(echarts)
方法,顾名思义就是初始化,但我没有看到任何关于这个init
方法的解释,于是产生怀疑,init
方法返回的对象可能并不是echarts实例对象。
于是,我又在插件文档上反复浏览,发现了下面这一段内容:
文档说明init
方法还有第二个参数的,第二个参数是回调函数,回调函数的参数才是chart实例。这是其一,还有一个很重要的观察点,既然这个方法都列在一起,说明它们的调用对象是同一个!!!而init
方法的调用对象是chartRef.value
,那么是不是说明setOption
方法的调用对象也是chartRef.value
,于是renderChart
方法代码改成如下:
1 | function renderChart() { |
再次启动项目,图表重新渲染成功!
所以~关键点就是setOption
的调用对象搞错了。。。
但也留下了一个疑问,为什么第一次渲染时,使用myChart.value.setOption
却可以。