前序
LZ 之前工作一直在用 Vue,但最近听说 Vue 新版也要 All IN JS,所以想着干脆换到 React 算了,所以目前在学习 React + TS + Hook,顺手拿了一个老项目重构,今天主要讲 React 封装 Echarts 公共组件, 因为第一次正式搞,所以本文中如果有 React 代码哪里不规范还请大佬们批评指正哈!
目录:
- 需求分析
- 技术评估
- 实现思路
- 测试优化
- 总结分享
1. 需求分析
-
ECharts
图表要用到很多,所以要将公共部分抽取成组件减少重复代码 -
ECharts
是需要操作到真实dom
的第三方库,和MVVM
框架一起使用需要做一些特殊处理 -
Vue
项目中使用的Vue-ECharts
,组件自动去处理实例挂、 数据更新等 -
React
也有echarts-for-react
等优秀的开源组件,但为了学习和更舒适的使用,我们需要自己去造新的轮子 -
参考资料:
- 【开源】echarts-for-react: https://hellohy.github.io/pos...
- 【开源】vue-echarts: https://github.com/ecomfe/vue...
- 【文章】如何在react中封装echarts :https://hellohy.github.io/pos...
2. 技术评估
我们需要用到四个东西:
- React
- React Hook
- Echarts
- TypeScript
3. 实现思路
1) 一个组件需要的参数配置 ChartProps
参数 | 值 | 描述 | 必填 |
---|---|---|---|
key | string | 用于保持多图表时每一个图表的独立性 | 是 |
option | object 或者 null | Echarts 配置参数 | 是 |
style | { width: string, height: string } |
用于保持多图表时每一个图表的独立性 | 是 |
className | string | 组件样式类 className | 否 |
onRender | onRender?(instance): void; | 渲染时回调函数,返回图表示例 | 否 |
2) 参数类型检查接口 interface
interface ChartProps {
key: string;
option: object | null;
style: {
width: string;
height: string;
};
className?: string;
onRender?(instance): void;
}
3) 基础组件 Chart.tsx
import * as React from "react";
import echarts from "echarts";
const Chart = (props: ChartProps): React.ReactElement => {
// 挂载节点
let chartDom = null;
// 元素挂载到浏览器事件
const refOnRender = (el): void => chartDom = el;
// 返回组件
return React.createElement("div", {
ref: refOnRender,
style: props.style,
className: props.className
});
};
export default Chart;
4) 当组件挂载到真实DOM
,初始化Echarts
实例,使用Hook
// 生命钩子函数
type Callback = () => void;
React.useEffect((): Callback => {
// 加载状态
function showLoading(instance): void {
instance.showLoading("default", {
text: "",
color: "#c23531",
textColor: "#000000",
maskColor: "rgba(255, 255, 255, 0.8)",
zlevel: 0
});
}
// 获取实例对象
let instance = echarts.getInstanceByDom(chartDom) || echarts.init(chartDom);
// 默认加载状态
showLoading(instance);
// 如果存在参数,渲染图表
if (props.option) {
// 关闭加载状态
if (instance) instance.hideLoading();
// 渲染图表
instance.setOption(props.option);
}
}, [props.option]);
5)浏览器窗口大小变化时图表大小自适应重绘
// 大小自适应
const resize = (): void => instance.resize();
window.removeEventListener("resize", resize);
window.addEventListener("resize", resize);
6) 给图表加动画需要图表实例,设置回调函数将图表实例返回
// 回调函数返回实例
if (props.onRender) props.onRender(instance);
7) 组件销毁时清除 监听器
和 组件状态值
// 销毁并清除状态
return (): void => {
echarts.dispose(instance);
window.removeEventListener("resize", resize);
};
8) 最终完整组件
import * as React from "react";
import echarts from "echarts";
/**
* 参数列表
* key: string; 唯一值
* option: object | null; 图表数据
* style: {
* width: string; 图表宽度
* height: string; 图表高度
* };
* className?: string; 图表CSS样式类名称
* onRender?(instance): void; 图表回调函数返回图表实例
*/
interface ChartProps {
key: string;
option: object | null;
style: {
width: string;
height: string;
};
className?: string;
onRender?(instance): void;
}
const Chart = (props: ChartProps): React.ReactElement => {
// 挂载节点
let chartDom = null;
// 生命钩子函数
type Callback = () => void;
React.useEffect((): Callback => {
console.log("useEffect");
// 加载状态
function showLoading(instance): void {
instance.showLoading("default", {
text: "",
color: "#c23531",
textColor: "#000000",
maskColor: "rgba(255, 255, 255, 0.8)",
zlevel: 0
});
}
// 获取实例对象
let instance = echarts.getInstanceByDom(chartDom) || echarts.init(chartDom);
// 大小自适应
const resize = (): void => instance.resize();
window.removeEventListener("resize", resize);
window.addEventListener("resize", resize);
// 默认加载状态
showLoading(instance);
// 渲染图表
if (props.option) {
if (instance) instance.hideLoading();
instance.setOption(props.option);
}
// 回调函数返回实例
if (props.onRender) props.onRender(instance);
// 销毁并清除状态
return (): void => {
echarts.dispose(instance);
window.removeEventListener("resize", resize);
};
}, [chartDom, props]);
// 元素挂载到浏览器事件
const refOnRender = (el): void => chartDom = el;
// 返回组件
return React.createElement("div", {
ref: refOnRender,
style: props.style,
className: props.className
});
};
// 导出组件模块
export default Chart;
测试优化
你可以:
- 自行搭建React+TS+Echarts开发环境
- 使用 Clone 本项目测试(暂不支持 sry)
主要代码 Test.tsx
import * as React from "react";
// 导入 Chart 组件
import Chart from "../chart";
const chartEmpty = {
title: {
text: "暂无数据",
show: true,
textStyle: {
color: "grey",
fontSize: 20
},
left: "center",
top: "center"
}
};
const Test= (): React.ReactElement => {
// 图表1数据
let [chart1Data, setChart1Data] = React.useState(null);
// 图表2数据
let [chart2Data, setChart1Data] = React.useState(null);
// 防护监控数据 实例
let [chart1, chart2] = [null, null];
// 模拟异步更新图表数据
function updateChart(): void{
let opts: null;
chartEmpty.title.text = "图表 1 暂无数据" + (+new Date());
opts = JSON.parse(JSON.stringify(chartEmpty));
setChart1Data(opts);
chartEmpty.title.text = "图表 2 暂无数据" + (+new Date());
opts = JSON.parse(JSON.stringify(chartEmpty));
setChart2Data(opts);
}
// 获取图表实例,添加自定义图表事件
React.useEffect((): void => {
console.log("chart1", chart1);
}, [chart1]);
// 返回组件
return (
<div>
<Chart
key="chart1"
className="chart1"
option={chart1Data}
onRender={(e): void => chart1 = e}
style={{width: "100%", height: "400px"}}/>
<hr>
<Chart
key="chart2"
className="chart2"
option={chart2Data}
style={{width: "100%", height: "400px"}}/>
<button onClick={updateChart}> 异步更新图表数据 </button>
</div>
)
}
export default Test;
总结分享
学习了React
组件的封装,由此及彼,以后其他第三方库应该也可以轻松集成了
学习了React Hook
使用方法。
学习了React + TS
的开发模式。
不到100行代码的组件相比于echarts-for-react
等其他优秀组件的少了很多高级和细节自定义。(开源的话会加进去吧)
欢迎大佬检阅。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。