You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 10:47:03