這里給大家分享我在網(wǎng)上總結(jié)出來(lái)的一些知識(shí),希望對(duì)大家有所幫助
按需導(dǎo)入的配置文件
配置文件這里就不再贅述,內(nèi)容都是一樣的,主打一個(gè)隨用隨取,按需導(dǎo)入。
import * as echarts from "echarts/core"; // 引入用到的圖表 import { LineChart, type LineSeriesOption} from "echarts/charts"; // 引入提示框、數(shù)據(jù)集等組件 import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, type TooltipComponentOption, type TitleComponentOption, type GridComponentOption, type LegendComponentOption } from "echarts/components"; // 引入標(biāo)簽自動(dòng)布局、全局過(guò)渡動(dòng)畫(huà)等特性 import { LabelLayout } from "echarts/features"; // 引入 Canvas 渲染器,必須 import { CanvasRenderer } from "echarts/renderers"; import type { ComposeOption } from "echarts/core"; // 通過(guò) ComposeOption 來(lái)組合出一個(gè)只有必須組件和圖表的 Option 類型 export type ECOption = ComposeOption< | LineSeriesOption | GridComponentOption | TitleComponentOption | TooltipComponentOption | LegendComponentOption >; // 注冊(cè)必須的組件 echarts.use([ LineChart, TitleComponent, TooltipComponent, GridComponent, CanvasRenderer, LabelLayout, LegendComponent ]); export default echarts;
基本封裝
DOM結(jié)構(gòu)和實(shí)例化
<script setup lang="ts"> import { Ref, onMounted, onBeforeUnmount } from "vue"; import { type EChartsType } from "echarts/core"; interface Props { option: ECOption; theme?: Object | string; // 主題 } const props = withDefaults(defineProps<Props>(), { theme: null }); const chartRef = ref<Ref<HTMLDivElement>>(null); const chartInstance = ref<EChartsType>(); // 繪制 const draw = () => { if (chartInstance.value) { chartInstance.value.setOption(props.option, { notMerge: true }); } }; // 初始化 const init = () => { if (!chartRef.value) return; // 校驗(yàn) Dom 節(jié)點(diǎn)上是否已經(jīng)掛載了 ECharts 實(shí)例,只有未掛載時(shí)才初始化 chartInstance.value = echarts.getInstanceByDom(chartRef.value); if (!chartInstance.value) { chartInstance.value = echarts.init( chartRef.value, props.theme, { renderer: "canvas" } ); draw(); } }; watch(props, () => { draw(); }); onMounted(() => { init(); }); onBeforeUnmount(() => { // 容器被銷毀之后,銷毀實(shí)例,避免內(nèi)存泄漏 chartInstance.value?.dispose(); }); </script> <template> <div id="echart" ref="chartRef" :style="{ width: '100px', height: '120px' }" /> </template>
chartRef
:當(dāng)前的 DOM 節(jié)點(diǎn),即 ECharts 的容器;
chartInstance
:當(dāng)前 DOM 節(jié)點(diǎn)掛載的 ECharts 實(shí)例,可用于調(diào)用實(shí)例上的方法,注冊(cè)事件,自適應(yīng)等;
draw
:用于繪制 ECharts 圖表,本質(zhì)是調(diào)用實(shí)例的 setOption 方法;
init
:初始化,在此獲取 DOM 節(jié)點(diǎn),掛載實(shí)例,注冊(cè)事件,并調(diào)用 draw
繪制圖表。
Cannot read properties of undefined (reading 'type')
請(qǐng)注意,上述代碼目前還不能正常運(yùn)行,這里會(huì)遇到第一個(gè)坑 —— 圖表無(wú)法顯示,這是 React 中沒(méi)有碰到的:
?出現(xiàn)這種問(wèn)題是因?yàn)椋覀兪褂?ref
?接收了?echarts.init
?的實(shí)例。這會(huì)導(dǎo)致?chartInstance
?被代理成為響應(yīng)式對(duì)象,影響了 ECharts 對(duì)內(nèi)部屬性的訪問(wèn)。Echarts 官方 FAQ?也闡述了該問(wèn)題:
?
所以,我們有兩種解決方法:
- 使用
shallowRef
替換ref
; - 使用
ref
+markRaw
。
shallowRef 和?ref()
?不同之處在于,淺層 ref 的內(nèi)部值將會(huì)原樣存儲(chǔ)和暴露,并且不會(huì)被深層遞歸地轉(zhuǎn)為響應(yīng)式。只有對(duì)?.value
?的訪問(wèn)是響應(yīng)式的。
而 markRaw 則會(huì)將一個(gè)對(duì)象標(biāo)記為不可被轉(zhuǎn)為代理。返回該對(duì)象本身。在有些值不應(yīng)該是響應(yīng)式的場(chǎng)景中,例如復(fù)雜的第三方類實(shí)例或 Vue 組件對(duì)象,這很有用。
?我們這里使用?markRaw
?對(duì)?init
?進(jìn)行包裹:
chartInstance.value = markRaw( echarts.init( chartRef.value, props.theme, { renderer: "canvas" } ) );
窗口防抖自適應(yīng)
這里和 React 中就差不多了,主要安利一個(gè) Vue 官方團(tuán)隊(duì)維護(hù)的 hooks 庫(kù):vueuse?。和 React 中的 ahooks 一樣,封裝了很多實(shí)用的 hooks,我們可以使用?useDebounceFn
?來(lái)優(yōu)化自適應(yīng)函數(shù):
import { useDebounceFn } from "@vueuse/core"; // 窗口自適應(yīng)并開(kāi)啟過(guò)渡動(dòng)畫(huà) const resize = () => { if (chartInstance.value) { chartInstance.value.resize({ animation: { duration: 300 } }); } }; // 自適應(yīng)防抖優(yōu)化 const debouncedResize = useDebounceFn(resize, 500, { maxWait: 800 }); onMounted(() => { window.addEventListener("resize", debouncedResize); }); onBeforeUnmount(() => { window.removeEventListener("resize", debouncedResize); });
額外監(jiān)聽(tīng)寬高
目前,圖標(biāo)的大小還是寫(xiě)死的,現(xiàn)在我們支持 props 傳遞寬高來(lái)自定義圖表大小:
interface Props { option: ECOption; theme?: Object | string; width: string; height: string; } <template> <div id="echart" ref="chartRef" :style="{ width: props.width, height: props.height }" /> </template>
請(qǐng)注意:在使用時(shí),我們必須指定容器的寬高,否則無(wú)法顯示,因?yàn)閳D表在繪制時(shí)會(huì)自動(dòng)獲取父容器的寬高。
flex/grid 布局下 resize 失效的問(wèn)題
這個(gè)問(wèn)題剛遇到著實(shí)有點(diǎn)蛋疼,摸了蠻久,而 bug 觸發(fā)的條件也比較奇葩,但也比較常見(jiàn):
- 在父組件中,復(fù)用多個(gè) ECharts 組件;
- 使用了 flex 或 grid 這種沒(méi)有明確給定寬高的布局;
此時(shí)會(huì)發(fā)現(xiàn):當(dāng)前窗口放大,正常觸發(fā) resize, 圖表會(huì)隨之放大。但是,此時(shí)再縮小窗口,雖然也會(huì)觸發(fā) resize,但是圖表的大小卻縮不回來(lái)了......
一開(kāi)始還以為是我封裝的寫(xiě)法有問(wèn)題,直到搜到了ECharts 官方的 issues 才發(fā)現(xiàn)原來(lái)不止我一個(gè)遇到了??
我的理解是:首先,無(wú)論什么布局 echarts 取的都是 dom 的 clientWidth 和 clientHeight 作為容器寬高。其次,由于 flex、grid 這種布局可以不需要顯示地指定 width、height,這就導(dǎo)致 echarts 在自適應(yīng)的過(guò)程中無(wú)法明確地獲取到容器的寬高,所以即便觸發(fā)了 resize 事件,但是重繪的圖表還是之前默認(rèn)的寬高。
解決方案
給每個(gè) flex-item
或 grid-item
自適應(yīng)的寬或者高都設(shè)置一個(gè)最小值(我項(xiàng)目中的寬是自適應(yīng)的,高度是固定的):
.chart-item { flex: 1; min-width: 30vh; height: 300px; }
這里不得不吐槽下,早在2017年就有人提出過(guò)這個(gè)問(wèn)題,2020年終于給出了解釋,但是現(xiàn)在都2023了,這個(gè)問(wèn)題還沒(méi)有得到解決,issues 還 open 著 ??
綁定鼠標(biāo)事件
我們可以給圖表中的一些組件添加額外的交互,比如給 title 鼠標(biāo) hover 事件等,記得在需要使用事件的組件上添加 triggerEvent: true
屬性。
我們演示鼠標(biāo)移入 title 顯示 y軸 name,鼠標(biāo)移出 title 隱藏 y軸 name 的需求:
interface Props { // 略... onMouseover?: (...args: any[]) => any; onMouseout?: (...args: any[]) => any; } const init = () => { // 略...... // 綁定 mousehover 事件: if (props.onMouseover) { chartInstance.value.on("mouseover", (event: Object) => { props.onMouseover(event, chartInstance.value, props.option); }); } // 綁定 mouseout 事件: if (props.onMouseout) { chartInstance.value.on("mouseout", (event: Object) => { props.onMouseout(event, chartInstance.value, props.option); }); } } };
import Chart from "@/components/BaseChart/index.vue"; import type { EChartsType } from "echarts/core"; import type { ECOption } from "@/components/BaseChart/config"; import type { YAXisOption } from "echarts/types/dist/shared"; // 鼠標(biāo)移入,顯示y軸 name const onMouseover = (chart: EChartsType, option: ECOption) => { (option.yAxis as YAXisOption).nameTextStyle.color = "#ccc"; // 重繪圖表 chart.setOption(option); }; // 鼠標(biāo)移出,隱藏y軸 name const onMouseout = (chart: EChartsType, option: ECOption) => { (option.yAxis as YAXisOption).nameTextStyle.color = "transparent"; chart.setOption(option); }; <template> <Chart width="100%" height="305px" :option="{ // 略...... title: { text: "標(biāo)題", triggerEvent: true }, }" :on-mouseover="onMouseover" :on-mouseout="onMouseout" /> </template>
展示 loading 動(dòng)畫(huà)
支持受控的 loading 動(dòng)畫(huà)
interface Props { // 略... loading?: boolean; // 受控 } const props = withDefaults(defineProps<Props>(), { theme: null, loading: false }); watch( () => props.loading, loading => { loading ? chartInstance.value.showLoading() : chartInstance.value.hideLoading(); } );
暴露實(shí)例方法
對(duì)父組件暴露獲取 ECharts 實(shí)例的方法,讓父組件可直接通過(guò)實(shí)例調(diào)用原生函數(shù)。
defineExpose({ getInstance: () => chartInstance.value, resize, draw });
順便提一下,?defineExpose
?是在?<script setup>
?才能使用的編譯器宏,用來(lái)顯式指定需要暴露給父組件的屬性。
完整代碼
太長(zhǎng)了,貼出來(lái)沒(méi)人會(huì)細(xì)看,有需要的直接自取,親測(cè)有效,啟動(dòng)項(xiàng)目就能看到,快去魔改吧??github文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-490576.html
本文轉(zhuǎn)載于:
https://juejin.cn/post/7245183742264377401
如果對(duì)您有所幫助,歡迎您點(diǎn)個(gè)關(guān)注,我會(huì)定時(shí)更新技術(shù)文檔,大家一起討論學(xué)習(xí),一起進(jìn)步。
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-490576.html
到了這里,關(guān)于記錄--Vue3 封裝 ECharts 通用組件的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!