React 封装 Echarts 公共组件

前序

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等优秀的开源组件,但为了学习和更舒适的使用,我们需要自己去造新的轮子
  • 参考资料:

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

原文链接:segmentfault.com

上一篇:使用 keep-alive 的 include 和 exclude 无效的一点注意
下一篇:2020前端秋招总结

相关推荐

  • (小小黑科技)vue+echarts实现半圆图表

    如何用echarts实现半圆图表?在echarts官方实例倒腾一波,发现官方并没有提供半圆图表的写法,那怎么办呢?官方没提供,但需求还是要实现的。 半圆图表其实就是饼图的一半,那么简单的思路如下:设...

    1 年前
  • 高频数据交换下Flutter与ReactNative的对比

    (标题图片来自网络,侵删) 后端使用go写的socketio服务模拟期货行情数据,每10ms推送10条行情数据 ReactNative已经尽力优化了。 Flutter由于没fluttersock...

    2 年前
  • 高级JavaScript:为什么这个函数用圆括号封装?[重复]

    Peter Mortensennerdess(https://stackoverflow.com/users/63550/petermortensen)提出了一个问题:Advanced JavaScr...

    2 年前
  • 高性能迷你React框架 anu1.3.0 发布

    anujs1.3.0是一款高性能Reactlike框架,是目前世界上对React16兼容最好的迷你库。 自React16起,相继推出createContext,createPortal, creat...

    2 年前
  • 高德地图 react-amap 实战

    clipboard.png(https://img.javascriptcn.com/5a33946ad8c0ea8ee7870f74f331d0c0 "clipboard.png") reacta...

    9 个月前
  • 高品质 React UI 组件

    (https://img.javascriptcn.com/cca319311c2ea59a2b5cdaa63b997828)(https://link.funteas.com?target=http...

    2 年前
  • 骚操作!在react中使用vuex

    原文地址(https://github.com/zyl1314/blog/issues/12) 前言 笔者最近在学习使用,提到react就绕不过去。redux是一个状态管理架构,被广泛用于rea...

    2 年前
  • 项目文档说明:react + Ant Design 的 blog-react-admin

    效果图(https://img.javascriptcn.com/734ce56fe3a37ab11e9744473f56bae1 "效果图") 前言 此 blogreactadmin 项目是基...

    2 年前
  • 项目实战-在Vant的基础上封装下拉日期控件

    需求分析 在实际项目中,表单里面的日期选择是常用的组件。Vant有提供日期组件,但是居然没有提供下拉形式的日期组件,不过该有的元件都有,就自己封装一个。 封装组件过程中我们要解决: 和表...

    1 年前
  • 项目中常用js封装(持续更新)

    我们日常开发中经常会碰到各种各样的需求,但很多需求都是重复的,因此我就把平时开发中遇到的一些常见方法做了个总结和归纳。 1、金额的格式化 比如2.00,1,222,2.00像这样格式的数据在很多电商...

    1 年前

官方社区

扫码加入 JavaScript 社区