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

vue-chartjs+WebSocket实时图表仅标签更新数据不显示问题排查

实时更新Vue-ChartJS图表数据异常排查与解决

问题描述

在Vue3+TypeScript项目中,使用vue-chartjs结合Chart.js通过WebSocket实时更新BTC价格图表时,出现以下异常:

  • chartData.value.datasets[0].data数组的值已通过Socket事件正确更新
  • 组件刷新时仅labels实时更新,数据数组无变化
    需求:每次Socket事件更新价格时,同步更新价格数据及对应时间戳。

原实现代码

<script lang="ts" setup>
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
} from 'chart.js'
import { onMounted, ref, watch } from 'vue';
import { Line } from 'vue-chartjs'

interface SocketData {
  bitcoin: string;
}

const bitcoinPrices = ref<number[]>([]);
const bitcoinLabels = ref<string[]>([]);

onMounted(async () => {
  const pricesWs = new WebSocket('wss://ws.coincap.io/prices?assets=bitcoin')

  pricesWs.onmessage = function (msg) {
    const socketData = JSON.parse(msg.data) as SocketData;
    const price = Number(socketData.bitcoin);
    bitcoinPrices.value = [...bitcoinPrices.value, price];
    bitcoinLabels.value = [...bitcoinLabels.value, new Date().toLocaleTimeString()];
    updateChartData();
  }
});

const {label, bgColor, title} = defineProps<{ label: string, bgColor: string, title: string }>();

ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend
);

const chartData = ref({
  labels: [] as string[], 
  datasets: [
    {
      label: label,
      data: [] as number[], 
      backgroundColor: bgColor,
      borderColor: bgColor,
      borderWidth: 2,
      pointBackgroundColor: bgColor
    },
  ],
});

const chartOptions =  { 
  plugins: {
    title: {
      display: true,
      text: title,
      font: {
        size: 24
      }
    }
  },
  maintainAspectRatio: false
};

function updateChartData() {
  chartData.value.labels = [...bitcoinLabels.value] 
  chartData.value.datasets[0].data = [...bitcoinPrices.value]; 
  chartData.value = { ...chartData.value }; 
}

watch([bitcoinPrices, bitcoinLabels], updateChartData);
</script>

<template>
  <div class="h-96">
    <Line
      :options="chartOptions"
      :data="chartData"
    />
  </div>
</template>

异常原因

  1. Vue响应式系统对嵌套对象的深层变更检测存在局限:直接修改datasets[0].data时,虽然数组内容更新,但datasets数组本身的引用未改变,Vue无法及时感知到深层数据变化
  2. 手动执行chartData.value = { ...chartData.value }的对象展开操作,无法确保触发vue-chartjs内部的图表实例更新机制

解决方案

方案一:替换整个datasets数组触发响应式更新

修改updateChartData函数,通过替换整个datasets数组,让Vue明确感知到数据变化:

function updateChartData() {
  chartData.value.labels = [...bitcoinLabels.value];
  // 替换整个datasets数组,保留原有配置仅更新data
  chartData.value.datasets = [
    {
      ...chartData.value.datasets[0],
      data: [...bitcoinPrices.value]
    }
  ];
}

方案二:调用Chart实例的update方法强制刷新

通过ref获取Line组件的Chart实例,手动调用update()方法强制图表刷新:

  1. 在模板中为Line组件添加ref:
<template>
  <div class="h-96">
    <Line
      ref="chartRef"
      :options="chartOptions"
      :data="chartData"
    />
  </div>
</template>
  1. 在script中定义ref并修改updateChartData:
const chartRef = ref<InstanceType<typeof Line> | null>(null);

function updateChartData() {
  chartData.value.labels = [...bitcoinLabels.value];
  chartData.value.datasets[0].data = [...bitcoinPrices.value];
  // 调用Chart实例的update方法强制刷新
  chartRef.value?.chart?.update();
}

额外优化

移除冗余的watch监听,因为Socket事件回调中已经直接调用了updateChartData,避免重复触发更新:

// 移除该行代码
// watch([bitcoinPrices, bitcoinLabels], updateChartData);

内容的提问来源于stack exchange,提问作者Pato P

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 08:36:07