一句话总结:切勿用 Vue 3 的
ref()包装 ECharts 实例!深度响应式代理(Deep Proxy)会导致 ECharts 内部上下文错乱,造成悬浮框 DOM 节点无法正常生成。
在 Vue 3 项目中渲染折线图/柱状图时,经常会遇到一个奇葩 Bug:
tooltip: { trigger: 'axis' },但鼠标悬浮完全不弹窗。TooltipView 组件,在页面中创建 HTML <div> 节点。ref() 的深度响应式代理(Proxy)许多自定义 Hook 或组件中常写出如下代码:
TypeScript
// ❌ 罪魁祸首:用 ref 包装了实例
const chartInstance = ref<EChartsType | null>(null)
function init() {
chartInstance.value = echarts.init(dom)
}
Vue 3 的 ref() 会将对象包裹为深度递归响应式(Deep Proxy)。
ECharts 实例内部包含极其复杂的原型链、私有变量与状态机。被 Proxy 劫持后:
this 上下文与 DOM 状态更新逻辑(如坐标系的 TooltipView),就会在代理层静默失败,导致 DOM 构造逻辑直接被中止。重构核心:彻底消除 ECharts 实例上的 Vue 响应式代理。
let 变量(最推荐、性能最好)如果图表实例只在 Hook 内部方法中使用,不需要在模板(Template)中直接绑定 UI:
TypeScript
// ✅ 最佳实践:使用纯原生 let 变量,零响应式开销
let chartInstance: EChartsType | null = null
function initChart(el: HTMLElement) {
if (chartInstance) chartInstance.dispose()
// 直接赋予原生实例
chartInstance = echarts.init(el)
}
function setOption(option: EChartsOption) {
chartInstance?.setOption(option)
}
shallowRef + markRaw(适用于必须导出 Ref 的场景)如果实例必须作为 Vue Ref 暴露给外部消费:
TypeScript
import { shallowRef, markRaw } from 'vue'
// ✅ 1. 使用 shallowRef 避免深层递归代理
const chartInstance = shallowRef<EChartsType | null>(null)
function initChart(el: HTMLElement) {
const rawInstance = echarts.init(el)
// ✅ 2. 使用 markRaw 显式声明“禁止代理”
chartInstance.value = markRaw(rawInstance)
}
ref() 或 reactive()。resize() 中误调用 clear(),否则同样会破坏 Canvas 的交互响应区(Hitbox)。AxisPointerComponent)导致的静默失效,优先采用全量引入 import * as echarts from 'echarts' 排除干扰。
评论 (0)