前序
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
等其他优秀组件的少了很多高级和细节自定义。(开源的话会加进去吧)
欢迎大佬检阅。