這可能是在 React 項目中使用 ECharts 的最佳實踐

項目經常會用到 echarts, 也嘗試過 echarts-for-react、原生封裝等實踐方案

但總覺得不夠完善,比如不能自適應容器尺寸,或者沒法開箱即用

最終我咬一咬牙,寫了一個 react-echarts-core

 

一、快速上手

把大象放進冰箱需要幾步?

打開冰箱、把大象放進去、關上冰箱,只要三步。

在 React 項目中使用 ECharts 圖表需要幾步?

同樣只要三步:

Step 1: 安裝 react-echarts-core

npm install react-echarts-core echarts  --save
#or
yarn add react-echarts-core echarts

Step 2: 創建圖表配置 option

import ChartCore from 'react-echarts-core';
import type { EChartsOption } from 'react-echarts-core';
​
const Demo = () => {
  const option: EChartsOption = {
    // ...
  };
}

Step 3: 使用 react-echarts-core 渲染 echarts 圖表

<ChartCore option={option} />

 

 

二、更多場景

1. 使用其他類型的圖表

react-echarts-core 默認支持餅圖(PieChart)、折線圖(LineChart)、柱狀圖(BarChart)

如果需要使用其他類型的圖表,需要使用內置的 use 函數引入

import { ScatterChart } from 'echarts/charts';
import ChartCore, { use } from 'react-echarts-core';

use([ScatterChart]);

const Demo = () => {
  // ...
}

除了上面三個基礎圖表類型以外,react-echarts-core 還內置了以下組件:

import { TooltipComponent, GridComponent, LegendComponent } from 'echarts/components';

同樣的,如需使用其他組件(如 DataZoomComponent ),也需要使用 use 函數註冊

 

2. 獲取 echarts 實例

react-echarts-core 提供了 onChartReady 回調函數,可以獲取 echarts 實例

可以通過 echarts 實例實現“事件處理”等操作

import React, { useCallback, useRef } from 'react';
import ChartCore from 'react-echarts-core';
import type { EChartsOption, EChartsType } from 'react-echarts-core';

const Demo: React.FC = () => {
  const chartInstance = useRef<EChartsType>();

  const option: EChartsOption = {
    // ...
  };

  // 綁定事件
  const bind = useCallback((ref: EChartsType) => {
    if (!ref) return;
    ref.on('click', params => {
      // do sth...
    });
  }, []);

  // 通過加載圖表成功的回調獲取 echarts 實例
  const onChartReady = useCallback((ref: EChartsType) => {
    chartInstance.current = ref;
    bind(ref);
  }, [bind]);

  return (
    <ChartCore option={option} onChartReady={onChartReady} />
  );
};

 

3. 更新時清除畫布

如果給 react-echarts-core 傳入 clear={true}, 則圖表數據更新時,會清除畫布

<ChartCore option={option} clear />

這個屬性在大部分情況下都用不上,但如果遇到 option 更新了但畫布沒有更新的情況,而且嘗試過其他方案後依舊無法解決,可以考慮使用 clear 來處理

 


以上便是 react-echarts-core 的用法

目前大部分 react 項目中使用 echarts 的方案,都沒有解決我的兩個需求痛點:

1. 圖表不能自適應容器

2. 不能開箱即用,需要額外引入 `echarts/core`  及其他 echarts 模塊

接下來會聊一聊我是怎麼解決這兩個問題的

 

三、開發歷程 - 自適應容器尺寸

echarts 本身提供了一個 resize 方法調整畫布尺寸,但最大的問題在於:什麼時候調用 echarts.resize

在項目實踐中,最常見的是 window.resize 導致容器尺寸變化,所以常規方式是監聽 window.resize 事件,調用 echarts.resize

但除此之外,也有可能窗口大小沒變,頁面內部的尺寸發生變化,比如最常見的“側邊欄展開/收起”

對於這種場景,監聽 window.resize 的方案就不再適用,如果能直接監聽容器的 resize 事件就完美了

好在 Web 技術發展迅速,還真有這麼一個監聽 DOM 尺寸變化的方法:Resize Observer API

不過作爲一個新技術,兼容性會稍差一點,這時候可以使用兼容方案: resize-observer-polyfill 或者 @juggle/resize-observer

最終我使用的是 @juggle/resize-observer, 因爲包體積更小...

 

四、開發歷程 - 註冊更多 echarts 模塊

爲達到開箱即用的效果,就需要在 react-echarts-core 內部註冊一些 echarts 基礎模塊,同時爲又不能影響開發者的外部擴展

註冊 echarts 模塊需要使用 echarts.use 方法,這個方法已經解決了模塊重複註冊的問題

但對於 'echarts/renderers' 卻存在問題

echarts 的圖表在渲染之前,需要引入 CanvasRenderer 或者 SVGRenderer 以明確圖表渲染的方案

也就是在第一次調用 echarts.use 的時候就需要指定 render,而後面傳入其他 render 是無效的

這麼一來,echarts 的 render 就鎖死了,無法在使用 react-echarts-core 時指定 render

 

最終我基於 echarts.use 封裝了一個新的 use 函數並暴露出來

這個函數以異步函數的形式統計一次事件循環中傳入的所有 echarts 模塊,這樣就能按實際配置選擇 render

const componentsList: ChartsComponents = [];
const renderList: EchartsRender[] = [];

function useEcharts(components: ChartsComponents, renders?: EchartsRender[]) {
  const baseComponents: ChartsComponents = [
    // ...
  ];
  const currentRender = renders?.[0] || CanvasRenderer;
  echarts.use([...baseComponents, ...components, currentRender]);
}

const debounceUseEcharts = debounce(useEcharts, 0);

export default function use(components?: ChartsComponents, render?: EchartsRender) {
  const main = () => {
    componentsList.push(...(Array.isArray(components) ? components : []));
    render && renderList.push(render);
    debounceUseEcharts(componentsList, renderList);
  };

  return main();
}

 

五、開發歷程 - CSS in JS

上面在處理 resize 的時候,在內部給 echarts DOM 節點加一個包裝容器,並寫了幾行基礎樣式

導致在引入 react-echarts-core 時,還需要引入一個 react-echarts-core.css 文件

這樣就違背了“減負”的初衷,有沒有什麼辦法能在引入 react-echarts-core 的同時引入樣式呢?

一個簡單的 CSS in JS 的方案在腦海中浮現~

export interface StyleItem {
  className: string,
  styles: CSSProperties,
}

function renderStyleItem(style: StyleItem): string {
  const className = style?.className || '';
  const id = `${STYLE_PREFIX}-${className}`;

  // 如果已創建過 style 則跳過
  if (window.document && !window.document.getElementById(id)) {
    const styleTag = document.createElement('style');
    styleTag.id = id;
    // style 對象轉字符串
    styleTag.innerText = getStyleText(className, style.styles);
    document.head.append(styleTag);
  }

  return className;
}

export default function renderStyle(styles: StyleItem | StyleItem[]) {
  const v: StyleItem[] = Array.isArray(styles) ? styles : [styles];
  return v.map(renderStyleItem).join(' ');
}

將原本的 css 文件改爲 StyleItem 對象的寫法,這樣就能在加載組件的時候,動態插入 <style> 標籤並寫入樣式

這樣就解決了額外引入 css 文件的問題,真正實現開箱即用~

 


 

最後奉上 GitHub 倉庫地址: https://github.com/wisewrong/react-echarts-core,歡迎 star or issue

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章