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

按讚的人:

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

按讚的人: