前言
在使用 Vue 3 和 ECharts 开发时,遇到了非常棘手的问题:
- 明明配置了
tooltip却无法正常显示。


- 配置了
dataZoom,进行滚动或缩放时无效且会报错:

解决方案:
创建 echarts实例 时使用 shallowRef 而非 ref
// 必要的导入
import { ref, shallowRef } from 'vue';
// 创建echarts实例
const chartInstance = ref();❌ // 错误
const chartInstance = shallowRef();✅ // 正常
好了,来这里解决问题的赶紧回去忙吧
接下来让我们深究一下为什么
这是一段常见的Vue 3组件代码:
<template>
<div ref="chartDom" style="width: 600px; height: 400px;"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
// 使用 ref 创建图表实例
const chartInstance = ref(null);
const chartDom = ref(null);
// 准备一个简单的图表配置项
const option = ref({
tooltip: { // tooltip配置
trigger: 'axis',
},
dataZoom: [{ // dataZoom配置
type: 'inside',
start: 0,
end: 100
}],
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'bar'
}]
});
// 初始化图表
onMounted(() => {
chartInstance.value = echarts.init(chartDom.value);
chartInstance.value.setOption(option.value);
});
// 销毁图表
onUnmounted(() => {
chartInstance.value?.dispose();
});
</script>
尽管这段代码逻辑清晰又足够简单,但运行后你会发现:
- 鼠标移动到柱状图上,
tooltip不会出现。 - 尝试使用鼠标滚轮或拖动进行缩放滚动(
dataZoom),图表毫无反应。
问题根因分析
问题的核心在于 ref() 与 shallowRef() 的区别。
-
ref()的工作原理:Vue 3的ref会对其接收的值进行深度响应式转换。这意味着它会递归地将一个对象的所有属性都转换为响应式对象,通过Proxy实现。 -
Echarts实例的特性:Echarts实例本身是一个复杂的、包含大量方法和属性的对象。这些方法(如
setOption,dispatchAction)是维持图表交互(如tooltip,dataZoom)的关键。 -
冲突发生:当我们使用
const chartInstance = ref(null)并随后将Echarts实例赋值给它时(chartInstance.value = echarts.init(...)),Vue会尝试对这个Echarts实例进行深度响应式代理。这个过程破坏和包裹了Echarts实例原有的方法和内部属性,导致其内部的
事件监听机制和函数执行上下文出现问题。因此,图表虽然能正常渲染,但所有依赖于原生实例方法的交互功能(如tooltip的显示隐藏、dataZoom的滚动事件处理)都会全部失效。
shallowRef的工作原理
shallowRef只会对.value属性本身进行响应式跟踪,而不会对其值(即我们赋给它的Echarts实例)进行深度响应式转换。它只关心整个实例对象的引用是否被替换(例如chartInstance.value = newInstance),而不关心对象内部的变化。
巧了不是,我们只需要知道图表实例的引用是否存在(以便在组件卸载时调用 dispose 方法),或者是否需要替换整个实例(如在图表类型切换时),而完全不需要Vue去深度监听实例内部的变化。
以后在遇到类似的bug,比如第三方库出现了未知的bug或者没有达到预期的效果那就都可以使用这种方案试一下。
总结
| 声明方式 | 响应式深度 | 对Echarts实例的影响 | 结果 |
|---|---|---|---|
ref() | 深度响应式 | 包裹并代理实例方法,破坏其内部机制 | tooltip, dataZoom 等交互失效 |
shallowRef() | 浅层响应式 | 仅跟踪实例引用变化,不触碰实例内部 | 功能正常,交互流畅 |
在Vue 3中管理Echarts、D3.js、Three.js等第三方库的实例时,一个非常重要的原则是:
除非明确需要,否则避免对复杂的第三方类实例进行深度响应式代理。
对于这类主要用于存储引用、调用方法,而其内部状态由自身管理的对象,shallowRef 是首选。它既保持了响应式的便利性(如判断实例是否存在),又完美避免了深度响应式带来的副作用。
2525

被折叠的 条评论
为什么被折叠?



