這可能是在 React 專案中使用 ECharts 的最佳實踐
但總覺得不夠完善,比如不能自適應容器尺寸,或者沒法開箱即用
最終我咬一咬牙,寫了一個
一、快速上手
把大象放進冰箱需要幾步?
開啟冰箱、把大象放進去、關上冰箱,只要三步。
在 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();
}
導致在引入 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