使用vue-chartjs构建响应式图表时触发“Maximum call stack size exceeded”错误
解决Vue3 + vue-chartjs v4中更新数据时的栈溢出问题
我之前在使用vue-chartjs配合Vue3做实时更新图表的时候,也碰到过几乎一模一样的栈溢出问题,咱们来一步步拆解原因和解决方案:
问题根源分析
你遇到的Maximum call stack size exceeded本质是响应式对象的无限递归访问,触发点主要在这几个地方:
- 你把已经是响应式的
props.device又用reactive重新包装了一遍,导致响应式嵌套,Vue在追踪依赖时出现混乱 chartLabels和chartValues用了reactive数组,当这些数组被传入chart.js后,chart.js内部的工具函数在访问数组属性时,反复触发Vue的响应式getter,形成无限递归- LineChart组件的template里错误地使用了
this.props,在<script setup>语法糖里this是不存在的,虽然这不是栈溢出的直接原因,但也是需要修正的问题
具体解决方案
1. 移除不必要的reactive包装
props本身已经是Vue3的响应式对象,不需要再用reactive二次包装,直接使用props.device即可:
// 删掉这行冗余代码 // const device = reactive<DeviceDistance>(props.device); // 直接在computed里用props.device const currentDistance = computed<number>((): number => { const tmpDistance = props.device.data?.distance; return tmpDistance && tmpDistance < 255 ? tmpDistance : 183; });
2. 将reactive数组替换为ref数组
reactive数组在被第三方库(比如chart.js)访问时,容易触发意外的响应式追踪,换成ref数组能避免这个问题:
// 把reactive改成ref const chartLabels = ref<string[]>([]); const chartValues = ref<number[]>([]); // 在setInterval里访问数组要加.value setInterval(() => { if (!currentDistance.value) return; chartLabels.value.push(new Date().toString()) chartValues.value.push(currentDistance.value) if (chartValues.value.length > 10) { chartLabels.value.shift(); chartValues.value.shift(); } }, 500)
3. 优化computed的图表数据返回
确保传给chart.js的是普通的JavaScript数组,而不是响应式包装对象,避免chart.js内部触发无限getter调用:
const chartDataComp = computed<ChartData>((): ChartData => { return { labels: [...chartLabels.value], // 展开成普通数组 datasets: [ { label: 'Distance', backgroundColor: '#f87979', tension: 0.5, data: [...chartValues.value] // 同样展开成普通数组 } ] } });
4. 修正LineChart组件的template语法
在<script setup>里不需要用this,直接访问props即可:
<template> <Line chartId="line-chart" :width="400" :height="200" :chart-data="props.chartData" :chart-options="props.chartOptions" :plugins="plugins" /> </template>
修改后的完整代码示例
LineChart.vue
<script setup lang="ts"> import { Line } from 'vue-chartjs' import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale } from 'chart.js' import type {ChartOptions,ChartData} from 'chart.js' interface Props { chartData: ChartData chartOptions: ChartOptions } const props = defineProps<Props>(); ChartJS.register( Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale ) const plugins = []; </script> <template> <Line chartId="line-chart" :width="400" :height="200" :chart-data="props.chartData" :chart-options="props.chartOptions" :plugins="plugins" /> </template>
DistanceWidget.vue
<script setup lang="ts"> import {computed, ref} from "vue"; import type DeviceDistance from "../../../model/DeviceDistance.js"; import LineChart from "../../chart/LineChart.vue"; import type {ChartData} from "chart.js"; interface Props { device: DeviceDistance } const props = defineProps<Props>(); const currentDistance = computed<number>((): number => { const tmpDistance = props.device.data?.distance; return tmpDistance && tmpDistance < 255 ? tmpDistance : 183; }); const chartLabels = ref<string[]>([]); const chartValues = ref<number[]>([]); setInterval(() => { if (!currentDistance.value) return; chartLabels.value.push(new Date().toString()) chartValues.value.push(currentDistance.value) if (chartValues.value.length > 10) { chartLabels.value.shift(); chartValues.value.shift(); } }, 500) const chartDataComp = computed<ChartData>((): ChartData => { return { labels: [...chartLabels.value], datasets: [ { label: 'Distance', backgroundColor: '#f87979', tension: 0.5, data: [...chartValues.value] } ] } }); const chartOptions = { responsive: true, maintainAspectRatio: false, } </script> <template> <LineChart :chartData="chartDataComp" :chartOptions="chartOptions" /> </template>
这样修改后,图表应该就能正常实时更新数据,不会再出现栈溢出的问题了。核心思路就是避免响应式对象的不必要嵌套,同时确保传给第三方库的是普通的JavaScript值,而不是响应式包装对象。
内容的提问来源于stack exchange,提问作者TiMESPLiNTER
相关产品推荐
相关产品推荐

