欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3封裝echarts組件的實(shí)現(xiàn)步驟

 更新時(shí)間:2025年01月13日 09:51:54   作者:Ja_dream  
這篇文章主要介紹了如何在Vue3中封裝一個(gè)高效、可復(fù)用的ECharts組件TChart,該組件支持響應(yīng)式圖表、空數(shù)據(jù)展示、事件監(jiān)聽(tīng)、主題切換和性能優(yōu)化等功能,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

1、引言

在現(xiàn)代Web應(yīng)用開發(fā)中,數(shù)據(jù)可視化已成為不可或缺的一部分。ECharts,作為一款強(qiáng)大的圖表庫(kù),提供了豐富的圖表類型和高度定制化的選項(xiàng),深受開發(fā)者喜愛(ài)。然而,在Vue項(xiàng)目中直接使用ECharts可能會(huì)遇到狀態(tài)管理、響應(yīng)式更新和組件化封裝的挑戰(zhàn)。本文將介紹如何在Vue3中封裝一個(gè)高效、可復(fù)用的ECharts組件——TChart。

2、組件亮點(diǎn)

  • 響應(yīng)式圖表:自動(dòng)調(diào)整大小以適應(yīng)容器。
  • 空數(shù)據(jù)展示:支持自定義空數(shù)據(jù)狀態(tài)顯示。
  • 事件監(jiān)聽(tīng):自動(dòng)綁定和解綁圖表事件。
  • 主題切換:動(dòng)態(tài)改變圖表主題。
  • 性能優(yōu)化:通過(guò)防抖函數(shù)減少不必要的渲染和資源消耗。

3、技術(shù)棧

  • Vue 3: 使用Composition API進(jìn)行狀態(tài)管理和邏輯組織。
  • ECharts: 數(shù)據(jù)可視化核心庫(kù)。
  • VueUse: 提供useResizeObserver等實(shí)用工具函數(shù)。

4、組件結(jié)構(gòu)

TChart組件的核心在于其模板和腳本部分:

  • 模板:包含圖表容器和空數(shù)據(jù)狀態(tài)展示插槽。
  • 腳本
    • 初始化圖表并設(shè)置選項(xiàng)。
    • 監(jiān)聽(tīng)窗口和圖表容器尺寸變化,實(shí)現(xiàn)響應(yīng)式布局。
    • 自動(dòng)綁定和解綁圖表事件。
    • 支持動(dòng)態(tài)主題切換和選項(xiàng)更新。

5、實(shí)現(xiàn)步驟

5.1 安裝echarts

npm install echarts

5.2 注冊(cè)echarts

并在 main 文件中注冊(cè)使用

import * as echarts from "echarts" // 引入echarts
app.config.globalProperties.$echarts = echarts // 全局使用

5.3 新建TChart組件

~components/TCharts.vue

<template>
  <div class="t-chart" v-bind="$attrs">
    <div
      v-show="!formatEmpty"
      class="t-chart-container"
      :id="id"
      ref="echartRef"
    />
    <slot v-if="formatEmpty" name="empty">
      <el-empty v-bind="$attrs" :description="description" />
    </slot>
    <slot></slot>
  </div>
</template>

<script setup lang="ts" name="TChart">
import {
  onMounted,
  getCurrentInstance,
  ref,
  watch,
  nextTick,
  onBeforeUnmount,
  markRaw,
  useAttrs,
} from 'vue'
import { useResizeObserver } from '@vueuse/core'
import { debounce, toLine } from '../../utils'
import { computed } from 'vue'
const { proxy } = getCurrentInstance() as any
const props = defineProps({
  options: {
    type: Object,
    default: () => ({}),
  },
  id: {
    type: String,
    default: () => Math.random().toString(36).substring(2, 8),
  },
  theme: {
    type: String,
    default: '',
  },
  isEmpty: {
    type: [Boolean, Function],
    default: false,
  },
  description: {
    type: String,
    default: '暫無(wú)數(shù)據(jù)',
  },
})

const echartRef = ref<HTMLDivElement>()
const chart = ref()
const emits = defineEmits()
const events = Object.entries(useAttrs())

// 圖表初始化
const renderChart = () => {
  chart.value = markRaw(proxy.$echarts.init(echartRef.value, props.theme))
  setOption(props.options)
  // 返回chart實(shí)例
  emits('chart', chart.value)

  // 監(jiān)聽(tīng)圖表事件
  events.forEach(([key, value]) => {
    if (key.startsWith('on') && !key.startsWith('onChart')) {
      const on = toLine(key).substring(3)
      chart.value.on(on, (...args) => emits(on, ...args))
    }
  })

  // 監(jiān)聽(tīng)元素變化
  useResizeObserver(echartRef.value, resizeChart)
  // 如果不想用vueuse,可以使用下邊的方法代替,但組件使用v-show時(shí),不會(huì)觸發(fā)resize事件
  // window.addEventListener('resize', resizeChart)
}

// 重繪圖表函數(shù)
const resizeChart = debounce(
  () => {
    chart.value?.resize()
  },
  300,
  true
)

// 設(shè)置圖表函數(shù)
const setOption = debounce(
  async (data) => {
    if (!chart.value) return
    chart.value.setOption(data, true, true)
    await nextTick()
    resizeChart()
  },
  300,
  true
)

const formatEmpty = computed(() => {
  if (typeof props.isEmpty === 'function') {
    return props.isEmpty(props.options)
  }
  return props.isEmpty
})

watch(
  () => props.options,
  async (nw) => {
    await nextTick()
    setOption(nw)
  },
  { deep: true }
)

watch(
  () => props.theme,
  async () => {
    chart.value.dispose()
    renderChart()
  }
)

onMounted(() => {
  renderChart()
})
onBeforeUnmount(() => {
  // 取消監(jiān)聽(tīng)
  // window.removeEventListener('resize', resizeChart)
  // 銷毀echarts實(shí)例
  chart.value.dispose()
  chart.value = null
})
</script>

<style lang="scss" scoped>
.t-chart {
  position: relative;
  width: 100%;
  height: 100%;
  &-container {
    width: 100%;
    height: 100%;
  }
}
</style>

utils/index.ts

type Func = (...args: any[]) => any
/**
 * 防抖函數(shù)
 * @param { Function } func 函數(shù)
 * @param { Number } delay 防抖時(shí)間
 * @param { Boolean } immediate 是否立即執(zhí)行
 * @param { Function } resultCallback
 */
export function debounce(
  func: Func,
  delay: number = 500,
  immediate?: boolean,
  resultCallback?: Func
) {
  let timer: null | ReturnType<typeof setTimeout> = null
  let isInvoke = false
  const _debounce = function (this: unknown, ...args: any[]) {
    return new Promise((resolve, reject) => {
      if (timer) clearTimeout(timer)
      if (immediate && !isInvoke) {
        try {
          const result = func.apply(this, args)
          if (resultCallback) resultCallback(result)
          resolve(result)
        } catch (e) {
          reject(e)
        }
        isInvoke = true
      } else {
        timer = setTimeout(() => {
          try {
            const result = func.apply(this, args)
            if (resultCallback) resultCallback(result)
            resolve(result)
          } catch (e) {
            reject(e)
          }
          isInvoke = false
          timer = null
        }, delay)
      }
    })
  }
  _debounce.cancel = function () {
    if (timer) clearTimeout(timer)
    isInvoke = false
    timer = null
  }
  return _debounce
}

/**
 * 節(jié)流函數(shù)
 * @param { Function } func
 * @param { Boolean } interval
 * @param { Object } options
 * leading:初始 trailing:結(jié)尾
 */
export function throttle(
  func: Func,
  interval: number,
  options = { leading: false, trailing: true }
) {
  let timer: null | ReturnType<typeof setTimeout> = null
  let lastTime = 0
  const { leading, trailing } = options
  const _throttle = function (this: unknown, ...args: any[]) {
    const nowTime = Date.now()
    if (!lastTime && !leading) lastTime = nowTime
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      lastTime = nowTime
      func.apply(this, args)
    }
    if (trailing && !timer) {
      timer = setTimeout(() => {
        lastTime = !leading ? 0 : Date.now()
        timer = null
        func.apply(this, args)
      }, remainTime)
    }
  }
  _throttle.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }
  return _throttle
}

/**
 * 駝峰轉(zhuǎn)換下劃線
 * @param { String } name
 */
export function toLine(name: string) {
  return name.replace(/([A-Z])/g, '_$1').toLowerCase()
}

6、使用組件

6.1使用示例

<template>
  <div>
    <el-button @click="isShow = !isShow">{{
      isShow ? '隱藏' : '顯示'
    }}</el-button>
    <el-button @click="addData()">增加數(shù)據(jù)</el-button>
    <t-chart
      v-show="isShow"
      :options="options"
      style="width: 100%; height: 500px"
      @click="click"
      @dblclick="addData()"
      @mousedown="mousedown"
      @mousemove="mousemove"
      @mouseover="mouseover"
      @mouseout="mouseout"
      @globalout="globalout"
      @contextmenu="contextmenu"
      @chart="chart"
    />
  </div>
</template>
<script setup lang="ts">
import TChart from '../components/TChart'
import { ref } from 'vue'
const options = ref({
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      data: [150, 230, 224, 218, 135, 147, 260],
      type: 'line',
    },
  ],
})

const isShow = ref(true)
const addData = () => {
  options.value.xAxis.data.push(
    'test' + Math.random().toString(36).substring(2, 8)
  )
  options.value.series[0].data.push(Math.random() * 200)
}
const click = (e) => {
  console.log('click-----', e)
}
const mousedown = (e) => {
  console.log('mousedown-----', e)
}
const mousemove = (e) => {
  console.log('mousemove-----', e)
}
const mouseover = (e) => {
  console.log('mouseover-----', e)
}
const mouseout = (e) => {
  console.log('mouseout-----', e)
}
const globalout = (e) => {
  console.log('globalout-----', e)
}
const contextmenu = (e) => {
  console.log('contextmenu-----', e)
}

const chartRef = ref(null)
const chart = (dom) => {
  chartRef.value = dom
}
</script>

6.2效果圖

更多使用示例,可以看看文檔 TChart 圖表

深入淺出---封裝思路

接下來(lái),我想和大家聊一下封裝思路。首先,封裝一個(gè)組件,我需要考慮什么,從哪方面考慮,讓我們看看ai的回答

封裝組件是前端開發(fā)中的一種常見(jiàn)實(shí)踐,它有助于提高代碼的復(fù)用性、可維護(hù)性和可讀性。在進(jìn)行組件封裝時(shí),應(yīng)遵循以下原則:

  • 單一職責(zé)原則:一個(gè)組件應(yīng)該只負(fù)責(zé)一項(xiàng)功能,這樣可以保證組件的簡(jiǎn)潔和可復(fù)用性。
  • 高內(nèi)聚低耦合:組件內(nèi)部的功能應(yīng)該緊密相關(guān)(高內(nèi)聚),而組件之間的依賴關(guān)系應(yīng)該盡量減少(低耦合)。這有助于提高組件的獨(dú)立性和靈活性。
  • 明確的輸入輸出:組件應(yīng)該有清晰的輸入(props)和輸出(state變化或事件)。這有助于其他開發(fā)者理解組件的使用方式。
  • 可復(fù)用性:設(shè)計(jì)組件時(shí)應(yīng)考慮到其復(fù)用性,避免硬編碼特定場(chǎng)景的邏輯,使得組件可以在多種場(chǎng)景下使用。
  • 可測(cè)試性:組件應(yīng)當(dāng)易于測(cè)試,可以通過(guò)單元測(cè)試和集成測(cè)試驗(yàn)證其功能。避免在組件中使用過(guò)多的全局狀態(tài)或復(fù)雜的異步操作,以降低測(cè)試難度。
  • 響應(yīng)式設(shè)計(jì):組件應(yīng)該能夠根據(jù)屏幕尺寸和其他環(huán)境因素自動(dòng)調(diào)整布局和樣式,提供良好的用戶體驗(yàn)。
  • 文檔和注釋:為組件編寫詳細(xì)的文檔和注釋,說(shuō)明組件的用途、輸入?yún)?shù)、返回值和使用示例,幫助其他開發(fā)者快速理解和使用組件。
  • 性能優(yōu)化:在可能的情況下,對(duì)組件進(jìn)行性能優(yōu)化,如使用虛擬DOM、避免不必要的重渲染等,提高應(yīng)用的運(yùn)行效率。
  • 一致性:保持組件風(fēng)格和行為的一致性,遵循項(xiàng)目的編碼規(guī)范和設(shè)計(jì)指南,提高整體的代碼質(zhì)量和用戶體驗(yàn)。
  • 模塊化:將大型組件分解成多個(gè)小的、可組合的子組件,每個(gè)子組件負(fù)責(zé)一部分功能,通過(guò)組合這些子組件來(lái)構(gòu)建更復(fù)雜的組件。

遵循這些原則可以幫助你創(chuàng)建出高質(zhì)量、可維護(hù)的組件,從而提升整個(gè)項(xiàng)目的開發(fā)效率和代碼質(zhì)量。

基于上邊的原則,我們?cè)倏紤]下封裝echarts的實(shí)現(xiàn)。

接下來(lái),我們來(lái)看一下官方的示例

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ECharts</title>
    <!-- 引入剛剛下載的 ECharts 文件 -->
    <script src="echarts.js"></script>
  </head>
  <body>
    <!-- 為 ECharts 準(zhǔn)備一個(gè)定義了寬高的 DOM -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
      // 基于準(zhǔn)備好的dom,初始化echarts實(shí)例
      var myChart = echarts.init(document.getElementById('main'));

      // 指定圖表的配置項(xiàng)和數(shù)據(jù)
      var option = {
        title: {
          text: 'ECharts 入門示例'
        },
        tooltip: {},
        legend: {
          data: ['銷量']
        },
        xAxis: {
          data: ['襯衫', '羊毛衫', '雪紡衫', '褲子', '高跟鞋', '襪子']
        },
        yAxis: {},
        series: [
          {
            name: '銷量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
          }
        ]
      };

      // 使用剛指定的配置項(xiàng)和數(shù)據(jù)顯示圖表。
      myChart.setOption(option);
    </script>
  </body>
</html>

實(shí)現(xiàn)的步驟的步驟有哪些?

  • 引入echarts
  • 定義一個(gè)DOM元素(容器)
  • 獲取DOM元素(容器)并初始化echarts實(shí)例
  • 指定圖表的配置項(xiàng)和數(shù)據(jù)
  • 使用剛指定的配置項(xiàng)和數(shù)據(jù)顯示圖表。

每當(dāng)我想使用echarts組件時(shí),都得經(jīng)過(guò)這五個(gè)步驟。當(dāng)我想實(shí)現(xiàn)多個(gè)圖表時(shí),這多個(gè)圖表對(duì)比起來(lái),哪些是步驟是變化的?哪些的不變的?

細(xì)心的網(wǎng)友會(huì)發(fā)現(xiàn),其中,變化最多的,是第四個(gè)步驟“圖表的配置項(xiàng)和數(shù)據(jù)”。那我,是不是可以將這些重復(fù)性的操作,封裝到組件里,讓組件替我去完成。

接下來(lái),讓我們來(lái)一步一步實(shí)現(xiàn)代碼

1.基本功能

1.1準(zhǔn)備DOM元素(容器)

<template>
  <div class="t-chart" v-bind="$attrs">
    <div v-show="!formatEmpty" class="t-chart" :id="id" ref="echartRef" />
</template>

<style lang="scss" scoped>
.t-chart {
  width: 100%;
  height: 100%;
}
</style>

1.2 獲取容器并初始化echarts實(shí)例

優(yōu)化小技巧:通過(guò)ref獲取dom實(shí)例比document操作獲取dom,性能更好

<template>
  <div class="t-chart" v-bind="$attrs">
    <div v-show="!formatEmpty" class="t-chart" :id="id" ref="echartRef" />
</template>
<script setup lang="ts" name="TChart">
import { onMounted, getCurrentInstance, ref, markRaw } from "vue"

const { proxy } = getCurrentInstance() as any
const props = defineProps({
  options: {
    type: Object,
    default: () => ({})
  },
  id: {
    type: String,
    default: () => Math.random().toString(36).substring(2, 8)
  }
})

const echartRef = ref<HTMLDivElement>()
const chart = ref()

// 圖表初始化
const renderChart = () => {
  chart.value = markRaw(proxy.$echarts.init(echartRef.value))
}

onMounted(() => {
  renderChart()
})
</script>
<style lang="scss" scoped>
.t-chart {
  width: 100%;
  height: 100%;
}
</style>

1.3 設(shè)置配置項(xiàng)和數(shù)據(jù)

<template>
  <div class="t-chart" v-bind="$attrs">
    <div v-show="!formatEmpty" class="t-chart" :id="id" ref="echartRef" />
</template>
<script setup lang="ts" name="TChart">
import { onMounted, getCurrentInstance, ref, markRaw } from "vue"

const { proxy } = getCurrentInstance() as any
const props = defineProps({
  options: {
    type: Object,
    default: () => ({})
  },
  id: {
    type: String,
    default: () => Math.random().toString(36).substring(2, 8)
  }
})

const echartRef = ref<HTMLDivElement>()
const chart = ref()

// 圖表初始化
const renderChart = () => {
  chart.value = markRaw(proxy.$echarts.init(echartRef.value))
  setOption(props.options)
}

// 設(shè)置圖表函數(shù)
const setOption = data => {
  chart.value.setOption(data, true, true)
  chart.value?.resize()
}

onMounted(() => {
  renderChart()
})
</script>
<style lang="scss" scoped>
.t-chart {
  width: 100%;
  height: 100%;
}
</style>

2.組件要實(shí)現(xiàn)的功能

很多時(shí)候,封裝封裝組件,并不是一次性就能做到很完美的狀態(tài),而是在使用中, 不斷去優(yōu)化,取改進(jìn)的。比如,在使用中,數(shù)據(jù)更新、頁(yè)面大小變化時(shí),圖表沒(méi)有重新渲染、echart事件沒(méi)有觸發(fā)。這些都是一點(diǎn)點(diǎn)去優(yōu)化改進(jìn)的。記住一個(gè)準(zhǔn)則:“先實(shí)現(xiàn)再優(yōu)化”

  • 響應(yīng)式圖表
  • 圖表尺寸的自適應(yīng)
  • 事件監(jiān)聽(tīng)
  • 性能優(yōu)化
  • 空數(shù)據(jù)展示
  • 插槽
  • 主題切換
  • 獲取echarts實(shí)例

3.響應(yīng)式圖表

希望數(shù)據(jù)變化時(shí),可以重新繪制圖表

// 重繪圖表函數(shù)
const resizeChart = debounce(
  () => {
    chart.value?.resize()
  },
  300,
  true
)

// 設(shè)置圖表函數(shù)
const setOption = debounce(
  async data => {
    if (!chart.value) return
    chart.value.setOption(data, true, true)
    await nextTick()
    resizeChart()
  },
  300,
  true
)

const formatEmpty = computed(() => {
  if (typeof props.isEmpty === "function") {
    return props.isEmpty(props.options)
  }
  return props.isEmpty
})
// 監(jiān)聽(tīng)數(shù)據(jù)變化時(shí),重繪
watch(
  () => props.options,
  async nw => {
    await nextTick()
    setOption(nw)
  },
  { deep: true }
)

4.圖表尺寸的自適應(yīng)

希望容器尺寸變化時(shí),圖表能夠自適應(yīng)

筆者這邊使用了vueuse的useResizeObserver,來(lái)實(shí)現(xiàn)對(duì)元素變化的監(jiān)聽(tīng),為什么沒(méi)用resize? 是因?yàn)槠渲杏锌印?/p>

1、window大小變化時(shí),才會(huì)觸發(fā)監(jiān)聽(tīng)

2、使用組件使用v-show的時(shí)候,不會(huì)觸發(fā),可能會(huì)蜷縮在一團(tuán)

import { useResizeObserver } from "@vueuse/core"

const renderChart = () => {
  chart.value = markRaw(proxy.$echarts.init(echartRef.value, props.theme))
  setOption(props.options)
  // 監(jiān)聽(tīng)元素變化
  useResizeObserver(echartRef.value, resizeChart)
  // 大小自適應(yīng)
  // window.addEventListener('resize', resizeChart)
}

onBeforeUnmount(() => {
  // 取消監(jiān)聽(tīng)
  // window.removeEventListener('resize', resizeChart)
})

5.事件監(jiān)聽(tīng)

通過(guò)useAttrs,拿到父組件傳過(guò)來(lái)的事件,并批量注冊(cè)emits事件

const events = Object.entries(useAttrs())

  // 監(jiān)聽(tīng)圖表事件
  events.forEach(([key, value]) => {
    if (key.startsWith('on') && !key.startsWith('onChart')) {
      const on = toLine(key).substring(3)
      chart.value.on(on, (...args) => emits(on, ...args))
    }
  })

6.性能優(yōu)化

  • 通過(guò)markRaw,將echarts實(shí)例標(biāo)記為普通對(duì)象,減少響應(yīng)式帶來(lái)的損耗。
  • 防抖函數(shù),用于圖表重繪和選項(xiàng)更新,減少不必要的調(diào)用,提高性能。
  • 當(dāng)組件被銷毀時(shí),調(diào)用 dispose 方法銷毀實(shí)例,防止可能的內(nèi)存泄漏。
chart.value = markRaw(proxy.$echarts.init(echartRef.value, props.theme))

  // 重繪圖表函數(shù)
const resizeChart = debounce(
  () => {
    chart.value?.resize()
  },
  300,
  true
)

// 設(shè)置圖表函數(shù)
const setOption = debounce(
  async data => {
    if (!chart.value) return
    chart.value.setOption(data, true, true)
    await nextTick()
    resizeChart()
  },
  300,
  true
)

onBeforeUnmount(() => {
  // 銷毀echarts實(shí)例
  chart.value.dispose()
  chart.value = null
})

6.空數(shù)據(jù)展示

組件可以通過(guò)isEmpty,來(lái)設(shè)置echarts圖表空狀態(tài),類型可以是Boolean,也可以是個(gè)函數(shù),方便靈活調(diào)用,還可以設(shè)置description,空數(shù)據(jù)時(shí)的展示文字

<template>
  <div class="t-chart" v-bind="$attrs">
    <div
      v-show="!formatEmpty"
      class="t-chart-container"
      :id="id"
      ref="echartRef"
    />
    <slot v-if="formatEmpty" name="empty">
      <el-empty v-bind="$attrs" :description="description" />
    </slot>
    <slot></slot>
  </div>
</template>
<script setup lang="ts" name="TChart">
const props = defineProps({
  isEmpty: {
    type: [Boolean, Function],
    default: false,
  },
  description: {
    type: String,
    default: '暫無(wú)數(shù)據(jù)',
  },
})

const formatEmpty = computed(() => {
  if (typeof props.isEmpty === 'function') {
    return props.isEmpty(props.options)
  }
  return props.isEmpty
})
  
...
</script>

7.插槽

可以通過(guò)插槽,在組件內(nèi)增加內(nèi)容,也可以替換空狀態(tài)的內(nèi)容

<template>
  <div class="t-chart" v-bind="$attrs">
    <div
      v-show="!formatEmpty"
      class="t-chart-container"
      :id="id"
      ref="echartRef"
    />
    <slot v-if="formatEmpty" name="empty">
      <el-empty v-bind="$attrs" :description="description" />
    </slot>
    <slot></slot>
  </div>
</template>

<style lang="scss" scoped>
.t-chart {
  position: relative;
  width: 100%;
  height: 100%;
  &-container {
    width: 100%;
    height: 100%;
  }
}
</style>

8.主題切換

監(jiān)聽(tīng)props的主題,動(dòng)態(tài)切換echarts 主題

const props = defineProps({
  theme: {
    type: String,
    default: '',
  }
})

// 圖表初始化
const renderChart = () => {
  chart.value = markRaw(proxy.$echarts.init(echartRef.value, props.theme))
  // ...
}

watch(
  () => props.theme,
  async () => {
    chart.value.dispose()
    renderChart()
  }
)

9.獲取echarts實(shí)例

注冊(cè)了echarts實(shí)例后,將實(shí)例返回給父組件

  chart.value = markRaw(proxy.$echarts.init(echartRef.value, props.theme))

  // 返回chart實(shí)例
  emits('chart', chart.value)

完整代碼

具體的,可以回看5.3 新建TChart組件

<template>
  <div class="t-chart" v-bind="$attrs">
    <div
      v-show="!formatEmpty"
      class="t-chart-container"
      :id="id"
      ref="echartRef"
    />
    <slot v-if="formatEmpty" name="empty">
      <el-empty v-bind="$attrs" :description="description" />
    </slot>
    <slot></slot>
  </div>
</template>

<script setup lang="ts" name="TChart">
import {
  onMounted,
  getCurrentInstance,
  ref,
  watch,
  nextTick,
  onBeforeUnmount,
  markRaw,
  useAttrs,
} from 'vue'
import { useResizeObserver } from '@vueuse/core'
import { debounce, toLine } from '../../utils'
import { computed } from 'vue'
const { proxy } = getCurrentInstance() as any
const props = defineProps({
  options: {
    type: Object,
    default: () => ({}),
  },
  id: {
    type: String,
    default: () => Math.random().toString(36).substring(2, 8),
  },
  theme: {
    type: String,
    default: '',
  },
  isEmpty: {
    type: [Boolean, Function],
    default: false,
  },
  description: {
    type: String,
    default: '暫無(wú)數(shù)據(jù)',
  },
})

const echartRef = ref<HTMLDivElement>()
const chart = ref()
const emits = defineEmits()
const events = Object.entries(useAttrs())

// 圖表初始化
const renderChart = () => {
  chart.value = markRaw(proxy.$echarts.init(echartRef.value, props.theme))
  setOption(props.options)
  // 返回chart實(shí)例
  emits('chart', chart.value)

  // 監(jiān)聽(tīng)圖表事件
  events.forEach(([key, value]) => {
    if (key.startsWith('on') && !key.startsWith('onChart')) {
      const on = toLine(key).substring(3)
      chart.value.on(on, (...args) => emits(on, ...args))
    }
  })

  // 監(jiān)聽(tīng)元素變化
  useResizeObserver(echartRef.value, resizeChart)
  // 大小自適應(yīng)
  // window.addEventListener('resize', resizeChart)
}

// 重繪圖表函數(shù)
const resizeChart = debounce(
  () => {
    chart.value?.resize()
  },
  300,
  true
)

// 設(shè)置圖表函數(shù)
const setOption = debounce(
  async (data) => {
    if (!chart.value) return
    chart.value.setOption(data, true, true)
    await nextTick()
    resizeChart()
  },
  300,
  true
)

const formatEmpty = computed(() => {
  if (typeof props.isEmpty === 'function') {
    return props.isEmpty(props.options)
  }
  return props.isEmpty
})

watch(
  () => props.options,
  async (nw) => {
    await nextTick()
    setOption(nw)
  },
  { deep: true }
)

watch(
  () => props.theme,
  async () => {
    chart.value.dispose()
    renderChart()
  }
)

onMounted(() => {
  renderChart()
})
onBeforeUnmount(() => {
  // 取消監(jiān)聽(tīng)
  // window.removeEventListener('resize', resizeChart)
  // 銷毀echarts實(shí)例
  chart.value.dispose()
  chart.value = null
})
</script>

<style lang="scss" scoped>
.t-chart {
  position: relative;
  width: 100%;
  height: 100%;
  &-container {
    width: 100%;
    height: 100%;
  }
}
</style>

最后看看是否符合組件的設(shè)計(jì)原則

以上,就是我實(shí)現(xiàn)echarts組件的思路。

總結(jié) 

到此這篇關(guān)于vue3封裝echarts組件的文章就介紹到這了,更多相關(guān)vue3封裝echarts組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Vue的ref特性的使用

    詳解Vue的ref特性的使用

    這篇文章主要介紹了詳解Vue的ref特性的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • Vue使用NPM方式搭建項(xiàng)目

    Vue使用NPM方式搭建項(xiàng)目

    這篇文章主要介紹了Vue項(xiàng)目搭建過(guò)程,使用NPM方式搭建的,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2018-10-10
  • Vue實(shí)現(xiàn)上拉加載下一頁(yè)效果的示例代碼

    Vue實(shí)現(xiàn)上拉加載下一頁(yè)效果的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用Vue實(shí)現(xiàn)上拉加載下一頁(yè)效果,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Vue有一定幫助,需要的可以參考一下
    2022-08-08
  • 如何封裝了一個(gè)vue移動(dòng)端下拉加載下一頁(yè)數(shù)據(jù)的組件

    如何封裝了一個(gè)vue移動(dòng)端下拉加載下一頁(yè)數(shù)據(jù)的組件

    這篇文章主要介紹了如何封裝了一個(gè)vue移動(dòng)端下拉加載下一頁(yè)數(shù)據(jù)的組件,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • 如何使用Vue的思想封裝一個(gè)Storage

    如何使用Vue的思想封裝一個(gè)Storage

    作為Web Storage API的接口,Storage 提供了訪問(wèn)特定域名下的會(huì)話存儲(chǔ)或本地存儲(chǔ)的功能,例如可以添加、修改或刪除存儲(chǔ)的數(shù)據(jù)項(xiàng),這篇文章主要給大家介紹了關(guān)于如何使用Vue的思想封裝一個(gè)Storage的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • 詳解vue之自行實(shí)現(xiàn)派發(fā)與廣播(dispatch與broadcast)

    詳解vue之自行實(shí)現(xiàn)派發(fā)與廣播(dispatch與broadcast)

    這篇文章主要介紹了詳解vue之自行實(shí)現(xiàn)派發(fā)與廣播(dispatch與broadcast),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • 淺析vue3的setup的使用和原理

    淺析vue3的setup的使用和原理

    setup是Vue3中引入的一個(gè)新的組件選項(xiàng),是Vue3中函數(shù)式組件的核心部分,它提供了一種新的方式來(lái)編寫組件邏輯,下面就來(lái)和大家講講它的使用和原理
    2023-08-08
  • el-table樹形數(shù)據(jù)量過(guò)大,導(dǎo)致頁(yè)面卡頓問(wèn)題及解決

    el-table樹形數(shù)據(jù)量過(guò)大,導(dǎo)致頁(yè)面卡頓問(wèn)題及解決

    這篇文章主要介紹了el-table樹形數(shù)據(jù)量過(guò)大,導(dǎo)致頁(yè)面卡頓問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • vue實(shí)現(xiàn)前端分頁(yè)完整代碼

    vue實(shí)現(xiàn)前端分頁(yè)完整代碼

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)前端分頁(yè)完整代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • vue實(shí)現(xiàn)導(dǎo)出excel的多種方式總結(jié)

    vue實(shí)現(xiàn)導(dǎo)出excel的多種方式總結(jié)

    在Vue中實(shí)現(xiàn)導(dǎo)出Excel有多種方式,可以通過(guò)前端實(shí)現(xiàn),也可以通過(guò)前后端配合實(shí)現(xiàn),這篇文章將為大家詳細(xì)介紹幾種常用的實(shí)現(xiàn)方式,需要的可以參考下
    2023-08-08

最新評(píng)論