Vue子组件ECharts报错:setOption不是函数的原因与解决
问题原因与解决方案
错误原因
你犯了一个关键错误:用同一个myChart ref同时绑定DOM元素和存储ECharts实例。
当父组件从服务器获取到新的gpuPower数据并传递给子组件时,Vue的响应式系统会触发子组件重渲染。此时模板里的<div ref="myChart">会重新将DOM元素赋值给myChart.value,直接覆盖了你之前通过echarts.init()创建的ECharts实例。所以定时器里调用myChart.value.setOption时,myChart.value已经变成了普通DOM节点,自然找不到setOption方法,导致报错。
修复方案
1. 拆分ref变量
把DOM容器和ECharts实例的存储分开,避免互相覆盖:
- 用
chartContainer存DOM元素 - 用
myChart存ECharts实例
2. 监听props更新
当父组件传递的gpuPower变化时,主动触发数据更新和图表刷新,而不是只依赖定时器轮询。
3. 清理定时器
组件卸载时清除定时器,防止内存泄漏。
修改后的完整代码
<template> <!-- 这里的ref改成chartContainer,专门存DOM --> <div ref="chartContainer" style="width: 100%; height: 400px;"></div> </template> <script setup> import { onMounted, ref, watch, onUnmounted } from 'vue'; import * as echarts from 'echarts'; const props = defineProps({ gpuPower: Object, // 明确类型,避免null导致的类型问题 isGPUPowerLoading: Boolean, }); // 拆分ref:一个存DOM容器,一个存ECharts实例 const chartContainer = ref(null); const myChart = ref(null); let seriesData = ref([]); let updateTimer = null; // 存储定时器ID,用于清理 const initChart = () => { // 用chartContainer的DOM初始化ECharts实例,存入myChart myChart.value = echarts.init(chartContainer.value); myChart.value.setOption({ title: { text: "Dynamic Data & Time Axis", }, tooltip: { trigger: "axis", formatter: (params) => { return params .map( (param) => `${param.seriesName}<br/>Time: ${param.name}<br/>Value: ${param.value[1].toFixed( 3 )}` ) .join("<br/>"); }, axisPointer: { animation: false, }, }, xAxis: { type: "time", boundaryGap: false, }, yAxis: { type: "value", boundaryGap: [0, "100%"], }, series: [], }); }; const generateNewData = () => { if (!props.gpuPower) return; // 先判断props是否存在,避免报错 for (const hostIp in props.gpuPower) { const hostData = props.gpuPower[hostIp]; if (!hostData?.data?.result || !Array.isArray(hostData.data.result)) continue; const resultData = hostData.data.result; resultData.forEach((gpuInfo) => { const uuid = gpuInfo.metric.UUID; const timestamp = gpuInfo.value[0]; const powerUsage = parseFloat(gpuInfo.value[1]); // 注意:timestamp单位是秒的话应该*1000,不是*10000 const date = new Date(timestamp * 1000); const currentSeriesData = seriesData.value.find((series) => series.uuid === uuid); if (currentSeriesData) { currentSeriesData.data.push({ name: date.toString(), value: [date, powerUsage] }); if (currentSeriesData.data.length > 2000) { currentSeriesData.data.shift(); } } else { seriesData.value.push({ name: gpuInfo.metric.modelName, uuid: uuid, type: 'line', showSymbol: false, data: [ { name: date.toDateString(), value: [date, powerUsage] } ] }) } }) } } const updateChart = () => { if (!myChart.value || !seriesData.value.length) return; myChart.value.setOption({ series: seriesData.value, }); }; const startDataGeneration = () => { // 先执行一次数据更新,避免等待1秒才显示 generateNewData(); updateChart(); updateTimer = setInterval(() => { generateNewData(); updateChart(); }, 1000); }; // 监听gpuPower变化,主动更新数据和图表 watch(() => props.gpuPower, () => { generateNewData(); updateChart(); }, { deep: true }); // 因为gpuPower是复杂对象,需要深度监听 onMounted(() => { // 初始化模拟数据 seriesData.value = [ { "name": "NVIDIA GeForce RTX 3090", "uuid": "GPU-eaff73fa-41bd-b365-3f0d-a3bb4c921c48", "type": "line", "showSymbol": false, "data": [ { "name": "Sun Sep 04 2518", "value": [ "2518-09-05T02:30:34.560Z", 17.668 ] } ] }, { "name": "NVIDIA RTX A6000", "uuid": "GPU-3a679677-ef7f-78ee-217e-a1552b8e79f1", "type": "line", "showSymbol": false, "data": [ { "name": "Sun Sep 04 2518", "value": [ "2518-09-05T02:30:34.489Z", 26.318 ] } ] }, { "name": "NVIDIA GeForce RTX 3090", "uuid": "GPU-ff3ad650-f347-686c-e061-a287e28136e2", "type": "line", "showSymbol": false, "data": [ { "name": "Sun Sep 04 2518", "value": [ "2518-09-05T02:30:34.489Z", 10.42 ] } ] }, { "name": "NVIDIA A100-PCIE-40GB", "uuid": "GPU-17f4ff58-3e4e-eb03-d93f-65eafd6c9068", "type": "line", "showSymbol": false, "data": [ { "name": "Sun Sep 04 2518", "value": [ "2518-09-05T02:30:34.489Z", 39.201 ] } ] }, { "name": "Tesla V100-PCIE-16GB", "uuid": "GPU-a4388fa4-5170-baea-3e17-cdc3b0adbf94", "type": "line", "showSymbol": false, "data": [ { "name": "Sun Sep 04 2518", "value": [ "2518-09-05T02:30:34.489Z", 36.936 ] } ] } ]; initChart(); startDataGeneration(); }); // 组件卸载时清除定时器 onUnmounted(() => { if (updateTimer) { clearInterval(updateTimer); } if (myChart.value) { myChart.value.dispose(); // 销毁ECharts实例,释放资源 } }); </script>
内容的提问来源于stack exchange,提问作者user28270014
相关产品推荐
相关产品推荐

