https://replit.com/@birdiewu/Ti-Zhong-Zhui-Zong-Ying-Yong-Cheng-Shi-online-weight-tracker?v=1
JS部分加上第46~49和55~58行無法更新圖表(官網提供的功能)
新增和刪除資料時,圖表無法跟著一起改變,只能重新整理網頁才會改變
不知道哪邊出了問題@@
看到這貼文,我研究了快一小時,我也卡關了,我也看不出為甚麼,哈哈
我 google 搜尋 chartjs vue RangeError: Maximum call stack size exceeded
發現這問題很多人遇到,算是一種地雷
會說是「地雷」是因為,光看你寫的程式碼,很合理,感覺根本沒什麼問題
但遇到的人都會耗上大量時間,才知道是 chartjs 底層或者 vuejs 底層有什麼「奇怪」的注意事項
程式開發就是這樣,有時候問題很不直觀。踩雷之後,就學個經驗,多踩就不怕了
我再找時間研究一下,有心得再跟大家說
(實務上,這種高互動的 UI library,都會自己出一套框架版本的,先處理掉地雷,實務上以使用框架版本為主)
看了幾篇 stackoverflow 大家的分享,都是使用 workaround
來描述解決方法
也就是這個 vuejs 跟 chartjs 的衝突,有點尷尬、難以漂亮解決
並不能根本解決,但能避開問題,有替代的解決方法
OK 花了不少時間,我終於研究完了
主要問題出在這裡
data() {
return {
weightChart: null,
// ...others
}
},
放在 data 這邊的,會被引進 vue 的 reactivity 機制
但是,我們並不希望 weightChart 被 vue 監聽、自動更新
我們希望透過 this.weightChart.update()
手動更新,也就是我們自己管理相關 state,然後手動去觸發更新
這種情況,把 weightChart 移到 data 外面即可,就變成普通的物件屬性,不會具有 reactivity
weightChart: null,
data() {
return {
// ...others
}
},
執行之後會發現,「新增」沒問題了,但是「刪除」依然有問題
繼續觀察程式碼,可能有關的是這裡
this.weightChart = new Chart(this.$refs.weightChartDOM, {
type: 'line',
data: {
labels: this.dates,
datasets: [{
label: '體重',
data: this.weights,
borderWidth: 1
}]
},
});
labels 與 datasets 期待的是陣列資料
使用 console.log(this.dates)
觀察變數型別,會發現我們傳進去的是帶有 vue reactivity 功能的 data(vue 使用 proxy 實作)
Chart.js 內部可能也有在監聽 變數
變化的機制,所以衝突
來試著把這邊的 reactivity 也拿掉。方法有很多種,我這邊用最土炮的一種:轉成 JSON 字串再轉回來
this.weightChart = new Chart(this.$refs.weightChartDOM, {
type: 'line',
data: {
labels: JSON.parse(JSON.stringify(this.dates)),
datasets: [{
label: '體重',
data: JSON.parse(JSON.stringify(this.weights)),
borderWidth: 1
}]
},
});
除此之外,在新增、刪除的最後
this.weightChart.data.labels = this.dates
this.weightChart.data.datasets[0].data = this.weights
this.weightChart.update()
也一併改掉,讓事情單純一點:我們都按照 Chart.js 期待的資料型態去傳
this.weightChart.data.labels = JSON.parse(JSON.stringify(this.dates))
this.weightChart.data.datasets[0].data = JSON.parse(JSON.stringify(this.weights))
this.weightChart.update()
大功告成!解決問題!
就跟其他眾多解法一樣:以上這做法,也只算是 workaround
因為我們沒有興趣深入研究 Chart.js 監聽 labels 跟 datasets 的變化機制
(通常只有 senior developers 會在這時候想深入進去搞懂)
既然都是 workaround,就隨便找個方法搞定就是了,解法不一定很有道理(我們根本沒有100%搞懂)
proxy 型別,一般來說,會跟普通的資料型別相容
也就是說,通常把 vue reactivity 狀態,直接傳給第三方套件,根本沒問題!
不需要這樣 JSON 轉來轉去的!這是一個剛好 vue 跟第三方套件衝突的特例!
實務上,這種高互動的 UI library,都會自己出一套框架版本的,先處理掉地雷
實務上以使用框架版本為主,所以不會太常碰到這種需要 workaround 的情況(但偶爾還是會遇到)
本課的原意是故意不用框架版本,練習看看手動整合,沒想到真的踩到地雷
(但是也好,也是一種練習)
在前端框架領域,整合框架 狀態
與 html 元件
、第三方外部元件
時,有兩種哲學
第一種哲學就是通通由框架處理好,這種元件稱之為 controlled components
。開發元件時比較吃力,但使用元件時比較輕鬆
第二種哲學就是,自己手動管理外部元件的狀態,這種元件稱之為 uncontrolled components
。開發元件時比較輕鬆,但使用元件時比較吃力
可以 google 搜尋 controlled components vs uncontrolled components
,看不懂沒關係,有更多使用套件的經驗之後,會開始看懂我在講什麼
修改後的 replit
我不確定錯誤的原因,推測是 vue 的 reactive value 的更新和 chart.js 的 update 形成無限遞迴或迴圈,導致新增或刪除時會產生以下錯誤
修改地方
shallowRef
87 行,阻止 vue 對 this.weightChart 進行深層監聽
Array Change Detection
52 - 54 行改用 non-mutating methods