把 Chart.js 的創建方法寫成 Composable

最近有點迷上 VUE3 的 composble 寫法,把可以重複利用的程式邏輯抽來變成composbal,要用的時候再匯入使用。今天我試著把 Chart.js 的創建方法抽出來放在 composables 中,沒想到竟然可行,以下整理一下我的作法。

甚麼是 Composables?官網 是這麼說的:

在 Vue 應用的概念中,”組合式函數”(Composables)是一個利用 Vue 的組合式 API 來封裝和複用有狀態邏輯的函數。

原本要使用 chart.js 都要在 組件中 new Chart,我想要把以下那一段搬到 composable 中,變成一個公用的函式,只要在元件中調用,傳 DOM 的 ref 跟圖表資料進去,就可以生成一個圖表。

dataChart.value = new Chart(chartElement.value.getContext('2d'), {參數物件})

起手式

在 Vite 中安裝完 Chart.js 後,記得要把 Chart.js 匯入 useChart.js。

import Chart from 'chart.js/auto'

結構如下:

import { ref, shallowRef } from 'vue'
import Chart from 'chart.js/auto'

export function useChart() {
  // composable 函式放這裡
  return { 回傳要調用的變數與函式 }
}

預計會在 useChart() 中放入初始化 (initChart)更新(chartUpdate) 這兩個Composable 函式,先來解釋初始化 (initChart)。

初始化

dataChart 要用來放 new Chart()後產生的實例,然後函式要傳入元件要生成圖表 Canvas ref 的 DOM 元素 ,以及圖表資料。

// 儲存 new Chart 生成的圖表實例
const dataChart = shallowRef(null);
const initChart = (element, dataObj) => {    
    if (dataChart.value) dataChart.value.destroy();
    dataChart.value = new Chart(element.getContext('2d'), dataObj);
 };

更新圖表

只要拿 dataChart 這個實例去調用 Chart.js 的 update()方法就可以了。

const chartUpdate = () => {
    dataChart.value.update();
};
useChart.js
import { ref, shallowRef } from 'vue';
import Chart from 'chart.js/auto';

export function useChart() {
  // 取得要放圖表的 canvas 元素
  //   const chartCanvas = ref(null)
  const dataChart = shallowRef(null);
  const initChart = (element, dataObj) => {
    // 儲存 new Chart 生成的圖表
    if (dataChart.value) dataChart.value.destroy();
    dataChart.value = new Chart(element.getContext('2d'), dataObj);
  };

  const chartUpdate = () => {
    dataChart.value.update();
  };

  return { initChart, chartUpdate };
}

組件中調用

首先要匯入 useChart.js,接著取出 initChart 跟 chartUpdate 這兩個函式。

import { useChart } from '../composables/useChart.js'
// 
const { initChart, chartUpdate } = useChart();

取出生成圖表 Canvas ref 的 DOM 元素:

// 取得要放圖表的 canvas 元素
const chartCanvas = ref(null);

初始化圖表:

// 只要傳 DOM element 跟 資料進去 initChart() 當參數就可以了
initChart(chartCanvas.value, dataObj);

更新圖表

在 watch 中觀察資料是否有更新,然後觸發chartUpdate();:

watch(
  dataArray,
  (newDataArray) => {
    alert('y');
    const nd = [...newDataArray];
    dataObj.data.datasets[0].data = nd;
    chartUpdate();
  },
  { deep: true }
);
要生成圖表的組件 chartItem.vue
<script setup>
import { ref, shallowRef, watch, nextTick, onMounted } from 'vue';
import { useChart } from '../composables/useChart.js';
// 取得要放圖表的 canvas 元素
const chartCanvas = ref(null);
const { initChart, chartUpdate } = useChart();
const dataArray = ref([50, 100, 20, 50]);
const dataObj = {
  type: 'line',
  data: {
    labels: ['鋼鐵人', '蜘蛛人', '美國隊長', '雷神'],
    datasets: [
      {
        label: '出勤次數',
        data: dataArray.value,
        backgroundColor: '#B7EDA1',
        borderColor: '#1BAD4F',
        borderWidth: 1,
        fill: true,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
  },
};
function getRandomInt() {
  return Math.floor(Math.random() * (50 - 5 + 1)) + 5;
}
const switchData = () => {
  alert('x');
  dataArray.value = [
    getRandomInt(),
    getRandomInt(),
    getRandomInt(),
    getRandomInt(),
  ];
};

watch(
  dataArray,
  (newDataArray) => {
    alert('y');
    const nd = [...newDataArray];
    dataObj.data.datasets[0].data = nd;
    chartUpdate();
  },
  { deep: true }
);

onMounted(() => {
  nextTick(() => {
    // 只要傳 DOM element 跟 資料進去 initChart() 當參數就可以了
    initChart(chartCanvas.value, dataObj);
  });
});
</script>

<template>
  <main>
    <button @click="switchData" style="margin-bottom: 20px">切換數據</button>
    <div class="canvas-box">
      <!-- 生成圖表的 canvas -->
      <canvas ref="chartCanvas"></canvas>
    </div>
  </main>
</template>

範例程式碼

Leave a Reply

Your email address will not be published. Required fields are marked *