https://replit.com/@birdiewu/Ti-Zhong-Zhui-Zong-Ying-Yong-Cheng-Shi-online-weight-tracker?v=1

JS部分加上第46~49和55~58行無法更新圖表(官網提供的功能)
新增和刪除資料時,圖表無法跟著一起改變,只能重新整理網頁才會改變
不知道哪邊出了問題@@


ฅ•ω•ฅ
按讚的人:

共有 6 則留言

修改後的 replit

我不確定錯誤的原因,推測是 vue 的 reactive value 的更新和 chart.js 的 update 形成無限遞迴或迴圈,導致新增或刪除時會產生以下錯誤

修改地方

shallowRef

87 行,阻止 vue 對 this.weightChart 進行深層監聽

Array Change Detection

52 - 54 行改用 non-mutating methods

按讚的人:

太感謝了!
我來研究一下

按讚的人:

看到這貼文,我研究了快一小時,我也卡關了,我也看不出為甚麼,哈哈

我 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,看不懂沒關係,有更多使用套件的經驗之後,會開始看懂我在講什麼

按讚的人:

感謝站長細心講解!!!
又學到一課👍👍👍(筆記下來)

按讚的人:

ฅ•ω•ฅ
🏆 本月排行榜
🥇
站長阿川
📝44   💬1  
629
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次