前序

LZ 之前工作一直在用 Vue,但最近听说 Vue 新版也要 All IN JS,所以想着干脆换到 React 算了,所以目前在学习 React + TS + Hook,顺手拿了一个老项目重构,今天主要讲 React 封装 Echarts 公共组件, 因为第一次正式搞,所以本文中如果有 React 代码哪里不规范还请大佬们批评指正哈!

目录:

  1. 需求分析
  2. 技术评估
  3. 实现思路
  4. 测试优化
  5. 总结分享

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

参数描述必填
keystring用于保持多图表时每一个图表的独立性
optionobject 或者 nullEcharts 配置参数
style{ width: string, height: string }用于保持多图表时每一个图表的独立性
classNamestring组件样式类 className
onRenderonRender?(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;

测试优化

你可以:

  1. 自行搭建React+TS+Echarts开发环境
  2. 使用 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等其他优秀组件的少了很多高级和细节自定义。(开源的话会加进去吧)
欢迎大佬检阅。