vue-chartjs中$refs引用chart赋值后调用update触发栈溢出原因探究
核心原因:Vue响应式Proxy与Chart.js内部遍历的冲突
直接调用this.$refs.myChart.chart.update()正常,赋值给this.chart后调用就栈溢出,本质是响应式代理导致的无限递归:
$refs的非响应性
Vue3中,组件的$refs是普通对象,不是响应式Proxy。this.$refs.myChart是原始的vue-chartjs组件实例,它的chart属性就是Chart.js的原始实例,没有被Vue的响应式系统包装。调用它的update()时,Chart.js内部的属性访问不会触发任何Vue的拦截逻辑,执行流程正常。组件属性的自动响应式包装
如果你的this.chart是在组件的data()中声明的,或者用ref()/reactive()创建的,当你把this.$refs.myChart.chart赋值给它时,Vue会自动将这个Chart实例包装成响应式Proxy。Chart.js的内部遍历触发无限递归
Chart.js的update()方法执行时,会深度遍历自身的配置、数据、上下文等内部对象,进行大量的属性读取操作。而被Proxy包装后的Chart实例,每一次属性读取都会触发Vue的getter拦截器,这个拦截器在处理过程中可能又会访问到Chart实例的其他属性,进而再次触发getter,形成无限递归调用,最终超出JavaScript的调用栈限制,抛出栈溢出错误。
解决办法
根据你的需求选择:
不需要响应式的情况:不在
data()/ref()中声明chart,直接在组件实例上挂载普通属性:// 比如在created或mounted中 this.chart = this.$refs.myChart.chart这样Vue不会对这个属性进行响应式包装,调用
this.chart.update()就和直接调用$refs的方式一致。需要保留响应式的场景:用
markRaw()标记Chart实例为非响应式,避免被Vue包装成Proxy:import { markRaw } from 'vue' // 赋值时 this.chart = markRaw(this.$refs.myChart.chart)这样既可以把Chart实例挂载到组件属性上,又不会触发响应式代理带来的递归问题。
内容的提问来源于stack exchange,提问作者rsd

