最近有點迷上 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>
