Vue3集成ChartJS更新数据时出现Maximum call stack size exceeded错误
Vue3 + ChartJS 更新数据时触发栈溢出的解决方法
问题分析
从报错栈能明确看到,Vue的响应式系统与ChartJS的数据访问逻辑形成了无限循环:ChartJS读取数据时触发Vue的响应式getter,而Vue的响应式拦截又会调用ChartJS的value属性访问,反复调用最终导致栈溢出。该问题仅在结合encryptedDataStore的响应式数据时出现,核心原因是直接将响应式对象传递给了ChartJS。
核心解决方案
给ChartJS传递非响应式的数据副本,切断响应式关联。可通过以下两种方式实现:
- 使用Vue提供的
toRaw()方法获取响应式对象的原始值 - 通过扩展运算符(
[...])或Array.from()拷贝数组,用{...obj}拷贝对象
针对两种实现的修改示例
1. 原生ChartJS实现修改
修改onMounted中的Chart初始化和数据更新逻辑,确保传递给Chart的是普通数据:
onMounted(() => { // 获取原始非响应式数据 const rawPasswordData = toRaw(encryptedDataStore.currentAndSafePasswords) const rawValueData = toRaw(encryptedDataStore.currentAndSafeValues) // 初始化Chart时使用非响应式数据 const chartInstance = new Chart( document.getElementById('passwordStrengthProgressChart') as HTMLCanvasElement, { type: 'line', data: { labels: [...rawPasswordData.current], // 拷贝数组切断响应式 datasets: [ { label: 'Secure Passwords', data: [...rawPasswordData.safe], borderColor: passwordColor, cubicInterpolationMode: 'monotone', tension: 0.4 }, { label: 'Secure Values', data: [1, 2], borderColor: valuesColor, cubicInterpolationMode: 'monotone' } ] }, options: { responsive: false, elements: { point: { radius: 0 } } } } ) // 测试更新时,同样传递非响应式数据 const interval = setInterval(() => { createTestData() // 更新数据时重新获取原始数据并拷贝 const newRawPasswordData = toRaw(encryptedDataStore.currentAndSafePasswords) const newRawValueData = toRaw(encryptedDataStore.currentAndSafeValues) chartInstance.data.labels = [...newRawPasswordData.current] chartInstance.data.datasets[0].data = [...newRawPasswordData.safe] chartInstance.data.datasets[1].data = [...newRawValueData.safe] chartInstance.update() // 手动触发Chart更新 clearInterval(interval) }, 5000) })
2. vue-chartjs实现修改
确保data返回的是普通对象,不要直接引用响应式Ref,同时监听数据变化并更新:
setup() { const encryptedDataStore: EncryptedDataStore = useEncryptedDataStore(); const passwordColor: string = PasswordsColor.primaryColor; const valuesColor: string = ValuesColor.primaryColor; // 定义普通ref存储Chart需要的数据,而非直接引用store的响应式数据 const chartData = ref({ labels: [], datasets: [ { label: 'Secure Passwords', data: [], borderColor: passwordColor, cubicInterpolationMode: 'monotone', tension: 0.4 }, { label: 'Secure Values', data: [], borderColor: valuesColor, cubicInterpolationMode: 'monotone', } ] }) const options: any = { responsive: true, elements: { point: { radius: 1 } } }; // 初始化和监听store数据变化,更新chartData的普通数据 const updateChartData = () => { const rawPasswordData = toRaw(encryptedDataStore.currentAndSafePasswords) const rawValueData = toRaw(encryptedDataStore.currentAndSafeValues) chartData.value.labels = [...rawPasswordData.current] chartData.value.datasets[0].data = [...rawPasswordData.safe] chartData.value.datasets[1].data = [...rawValueData.safe] } onMounted(() => { updateChartData() const interval = setInterval(() => { createTestData() updateChartData() // 调用更新方法 clearInterval(interval); }, 5000); }) // 如果store数据会自动变化,添加watch监听 watch( () => [encryptedDataStore.currentAndSafePasswords, encryptedDataStore.currentAndSafeValues], () => { updateChartData() }, { deep: true } ) return { options, data: chartData } }
关键注意点
- 禁止直接将Vue的响应式Ref/Reactive对象传递给ChartJS,必须转换成普通数据
- 使用
toRaw()时要确保仅用于读取原始数据,避免意外修改响应式状态 - 对于vue-chartjs,传递的
data需是普通响应式对象(内部数据为非响应式的数组/对象),而非嵌套的响应式Ref
内容的提问来源于stack exchange,提问作者Tyler Wanta
相关产品推荐
相关产品推荐

