React+TS前台项目实战(二十六)-- 高性能可配置Echarts图表组件封装

文章目录

  • 前言
  • CommonChart组件
    • 1. 功能分析
    • 2. 代码+详细注释
    • 3. 使用到的全局hook代码
    • 4. 使用方式
    • 5. 效果展示
  • 总结


前言

Echarts图表在项目中经常用到,然而,重复编写初始化,更新,以及清除实例等动作对于开发人员来说是一种浪费时间和精力。因此,在这篇文章中,将封装一个 “高性能可配置Echarts组件” ,简化开发的工作流程,提高数据可视化的效率和质量。

CommonChart组件

1. 功能分析

(1)可以渲染多种类型的图表,包括折线图、柱状图、饼图、地图和散点图
(2)通过传入的 option 属性,配置图表的各种参数和样式
(4)通过传入的 onClick 属性,处理图表元素的点击事件
(5)通过传入的 notMerge 属性,控制是否合并图表配置
(6)通过传入的 lazyUpdate 属性,控制是否懒渲染图表
(7)通过传入的 style 属性,设置图表容器的样式
(8)通过传入的 className 属性,自定义图表容器的额外类名
(9)通过监听窗口大小变化,自动调整图表的大小
(10)使用 usePrevious、useWindowResize 和 useEffect 等钩子来提高组件性能并避免不必要的渲染

2. 代码+详细注释

// @/components/Echarts/commom/index.tsx
import { useRef, useEffect, CSSProperties } from "react";
// 引入 Echarts 的各种图表组件和组件配置,后续备用
import "echarts/lib/chart/line"; // 折线图
import "echarts/lib/chart/bar"; // 柱状图
import "echarts/lib/chart/pie"; // 饼图
import "echarts/lib/chart/map"; // 地图
import "echarts/lib/chart/scatter"; // 散点图
import "echarts/lib/component/tooltip"; // 提示框组件
import "echarts/lib/component/title"; // 标题组件
import "echarts/lib/component/legend"; // 图例组件
import "echarts/lib/component/markLine"; // 标线组件
import "echarts/lib/component/dataZoom"; // 数据区域缩放组件
import "echarts/lib/component/brush"; // 刷选组件
// 引入 Echarts 的类型声明
import * as echarts from "echarts";
import { ECharts, EChartOption } from "echarts";
// 引入自定义的钩子函数和公共函数
import { useWindowResize, usePrevious } from "@/hooks";
import { isDeepEqual } from "@/utils";
/**
 * 公共 Echarts 业务灵巧组件,可在项目中重复使用
 *
 * @param {Object} props - 组件属性
 * @param {EChartOption} props.option - Echarts 配置项
 * @param {Function} [props.onClick] - 点击事件处理函数
 * @param {boolean} [props.notMerge=false] - 是否不合并数据
 * @param {boolean} [props.lazyUpdate=false] - 是否懒渲染
 * @param {CSSProperties} [props.style] - 组件样式
 * @param {string} [props.className] - 组件类名
 * @returns {JSX.Element} - React 组件
 */
type Props = {
  option: EChartOption;
  onClick?: (param: echarts.CallbackDataParams) => void;
  notMerge?: boolean;
  lazyUpdate?: boolean;
  style?: CSSProperties;
  className?: string;
};
const CommonChart = (props: Props) => {
  // 解构属性,并设置默认值
  const {
    option,
    onClick, // 点击事件处理函数
    notMerge = false, // 是否不合并数据,默认为 false
    lazyUpdate = false, // 是否懒渲染,默认为 false
    style, // 组件样式
    className = "", // 组件类名,默认为空字符串
  } = props;
  // 创建 ref 来引用 div 元素,并初始化 chartInstanceRef 为 null
  const chartRef = useRef<HTMLDivElement>(null);
  const chartInstanceRef = useRef<ECharts | null>(null);
  // 使用 usePrevious 钩子函数来记录上一次的 option 和 onClick 值
  const prevOption = usePrevious(option);
  const prevClickEvent = usePrevious(onClick);

  useEffect(() => {
    // 定义一个变量来存储图表实例
    let chartInstance: ECharts | null = null;
    if (chartRef.current) {
      // 如果图表实例不存在,则初始化
      if (!chartInstanceRef.current) {
        const hasRenderInstance = echarts.getInstanceByDom(chartRef.current);
        if (hasRenderInstance) {
          hasRenderInstance.dispose();
        }
        chartInstanceRef.current = echarts.init(chartRef.current);
      }
      // 暂存当前的图表实例
      chartInstance = chartInstanceRef.current;
      // 如果 option 或 onClick 值发生变化,则重新渲染
      try {
        if (!isDeepEqual(prevOption, option, ["formatter"])) {
          chartInstance.setOption(option, { notMerge, lazyUpdate });
        }
        if (onClick && typeof onClick === "function" && onClick !== prevClickEvent) {
          chartInstance.on("click", onClick);
        }
      } catch (error) {
        chartInstance && chartInstance.dispose();
      }
    }
  }, [option, onClick, notMerge, lazyUpdate, prevOption, prevClickEvent]);
  // 监听窗口大小变化,当窗口大小变化时,重新渲染图表
  useWindowResize(() => {
    if (chartInstanceRef.current) {
      chartInstanceRef.current?.resize();
    }
  });
  return <div style={{ ...style }} className={className} ref={chartRef}></div>;
};

export { CommonChart };

3. 使用到的全局hook代码

// @/utils/index
// 深度判断两个对象某个属性的值是否相等
export const isDeepEqual = (left: any, right: any, ignoredKeys?: string[]): boolean => {
  const equal = (a: any, b: any): boolean => {
    if (a === b) return true

    if (a && b && typeof a === 'object' && typeof b === 'object') {
      if (a.constructor !== b.constructor) return false

      let length
      let i
      if (Array.isArray(a)) {
        length = a.length
        if (length !== b.length) return false
        for (i = length; i-- !== 0;) {
          if (!equal(a[i], b[i])) return false
        }
        return true
      }

      if (a instanceof Map && b instanceof Map) {
        if (a.size !== b.size) return false
        for (i of a.entries()) {
          if (!b.has(i[0])) return false
        }
        for (i of a.entries()) {
          if (!equal(i[1], b.get(i[0]))) return false
        }
        return true
      }

      if (a instanceof Set && b instanceof Set) {
        if (a.size !== b.size) return false
        for (i of a.entries()) if (!b.has(i[0])) return false
        return true
      }

      if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags
      if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf()
      if (a.toString !== Object.prototype.toString) return a.toString() === b.toString()

      const keys = Object.keys(a)
      length = keys.length
      if (length !== Object.keys(b).length) return false

      for (i = length; i-- !== 0;) {
        if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false
      }

      for (i = length; i-- !== 0;) {
        const key = keys[i]

        if (key === '_owner' && a.$$typeof) {
          // React
          continue
        }

        if (ignoredKeys && ignoredKeys.includes(key)) {
          continue
        }

        if (!equal(a[key], b[key])) return false
      }

      return true
    }
    // eslint-disable-next-line no-self-compare
    return a !== a && b !== b
  }
  return equal(left, right)
}
--------------------------------------------------------------------------
// @/hooks/index.ts
/**
 * Returns the value of the argument from the previous render
 * @param {T} value
 * @returns {T | undefined} previous value
 * @see https://react-hooks-library.vercel.app/core/usePrevious
 */
export function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>()

  useEffect(() => {
    ref.current = value
  }, [value])

  return ref.current
}

export function useWindowResize(callback: (event: UIEvent) => void) {
  useEffect(() => {
    window.addEventListener('resize', callback)
    return () => window.removeEventListener('resize', callback)
  }, [callback])
}

4. 使用方式

// 引入组件和echarts
import { CommonChart } from "@/components/Echarts/common";
import echarts from "echarts/lib/echarts";
// 使用
const useOption = () => {
  return (data: any): echarts.EChartOption => {
    return {
      color: ["#ffffff"],
      title: {
        text: "图表y轴时间",
        textAlign: "left",
        textStyle: {
          color: "#ffffff",
          fontSize: 12,
          fontWeight: "lighter",
          fontFamily: "Lato",
        },
      },
      grid: {
        left: "2%",
        right: "3%",
        top: "15%",
        bottom: "2%",
        containLabel: true,
      },
      xAxis: [
        {
          axisLine: {
            lineStyle: {
              color: "#ffffff",
              width: 1,
            },
          },
          data: data.map((item: any) => item.xTime),
          axisLabel: {
            formatter: (value: string) => value,
          },
          boundaryGap: false,
        },
      ],
      yAxis: [
        {
          position: "left",
          type: "value",
          scale: true,
          axisLine: {
            lineStyle: {
              color: "#ffffff",
              width: 1,
            },
          },
          splitLine: {
            lineStyle: {
              color: "#ffffff",
              width: 0.5,
              opacity: 0.2,
            },
          },
          axisLabel: {
            formatter: (value: string) => new BigNumber(value),
          },
          boundaryGap: ["5%", "2%"],
        },
        {
          position: "right",
          type: "value",
          axisLine: {
            lineStyle: {
              color: "#ffffff",
              width: 1,
            },
          },
        },
      ],
      series: [
        {
          name: t("block.hash_rate"),
          type: "line",
          yAxisIndex: 0,
          lineStyle: {
            color: "#ffffff",
            width: 1,
          },
          symbol: "none",
          data: data.map((item: any) => new BigNumber(item.yValue).toNumber()),
        },
      ],
    };
  };
};
const echartData = [
  { xTime: "2020-01-01", yValue: "1500" },
  { xTime: "2020-01-02", yValue: "5220" },
  { xTime: "2020-01-03", yValue: "4000" },
  { xTime: "2020-01-04", yValue: "3500" },
  { xTime: "2020-01-05", yValue: "7800" },
];
const parseOption = useOption();
<CommonChart
  option={parseOption(echartData, true)}
  notMerge
  lazyUpdate
  style={{
    height: "180px",
  }}
></ChartBlock>

5. 效果展示

在这里插入图片描述


总结

下一篇讲【首页响应式搭建以及真实数据渲染】。关注本栏目,将实时更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/784793.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言相关内容模块

C语言相关内容模块 1、函数指针定义方式 1、函数指针定义方式 函数指针的具体用法

最近点对问题(算法与数据结构设计)

课题内容和要求 最近点对问题&#xff0c;在二维平面上输入n个点列P。其中任一点pi&#xff08;xi&#xff0c;yi&#xff09;&#xff0c;编写程序求出最近的两个点。使用穷举法实现&#xff0c;算法复杂度O(n2)&#xff1b;优化算法&#xff0c;以O(nlog2n)实现这一问题 数…

阶段三:项目开发---民航功能模块实现:任务24:航空实时监控

任务描述 内 容&#xff1a;地图展示、飞机飞行轨迹、扇区控制。航空实时监控&#xff0c;是飞机每秒发送坐标&#xff0c;经过终端转换实时发送给塔台&#xff0c;为了飞机位置的精准度&#xff0c;传输位置的密度很大&#xff0c;在地图位置显示不明显。本次为了案例展示效…

AI系统的PyTorch:TextGrad框架基于文本梯度实现大语言模型AI系统自优化!

AI系统的PyTorch&#xff1a;TextGrad框架基于文本梯度实现大语言模型AI系统自优化&#xff01; 原创 旺知识 旺知识 2024年07月07日 16:21 广东 人工智能&#xff08;AI&#xff09;正在经历一场范式转变&#xff0c;这一转变是由系统协调多个大型语言模型&#xff08;LLMs&…

51 单片机[7]:计时器

一、定时器 1. 定时器介绍 51单片机的定时器属于单片机的内部资源&#xff0c;其电路的连接和运转均在单片机内部完成。 定时器作用&#xff1a; &#xff08;1&#xff09;用于计时系统&#xff0c;可实现软件计时&#xff0c;或者使程序每隔一固定时间完成一项操作 &#…

【零基础】学JS之APIS(基于黑马)

喝下这碗鸡汤 披盔戴甲,一路勇往直前! 1. 什么是事件 事件是在编程时系统内发生的动作或者发生的事情 比如用户在网页上单击一个按钮 2. 什么是事件监听? 就是让程序检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应&#xff0c;也称为 注…

【人工智能】—基于成都市各区(市)县租房价格预测建模研究

引言 随着城市化进程的加速&#xff0c;人口流动日益频繁&#xff0c;租房市场作为城市生活的重要组成部分&#xff0c;其价格波动对居民生活质量和城市经济发展具有显著影响。成都市&#xff0c;作为中国西部地区的经济、文化、交通和科技中心&#xff0c;近年来吸引了大量人…

5.Python学习:面向对象

1.面向对象和面向过程的区别 以下五子棋为例&#xff1a; 2.类和实例 &#xff08;1&#xff09;类是抽象的模板&#xff0c;实例是根据模板创建出来的具体的对象 &#xff08;2&#xff09;比如人类就是一个类&#xff0c;刘亦菲就是人类的一个实例 2.1 新建类和类的实例…

王老师 linux c++ 通信架构 笔记(三)安装 xftp、

&#xff08;11&#xff09;调整 xshell 终端的字体大小&#xff0c;默认字体大小是 9 &#xff1a; &#xff08;12&#xff09; 共享文件夹 hgfs 的含义&#xff1a; &#xff08;13&#xff09;安装 xftp &#xff0c; 傻瓜式安装&#xff0c;出了修改下默认安装位置。 操作…

上位机图像处理和嵌入式模块部署(mcu项目2:串口日志记录器)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 淘宝上面有一个商品蛮好玩的&#xff0c;那就是日志记录器。说是记录器&#xff0c;其实就是一个模块&#xff0c;这个模块的输入是一个ttl串口&am…

18.动态规划之斐波那契数列模型1

1.第N个斐波那契数 1137. 第 N 个泰波那契数 - 力扣&#xff08;LeetCode&#xff09; 做题流程 1. 状态表示&#xff1a; 这道题可以【根据题目的要求】直接定义出状态表示&#xff1a; dp[i] 表示&#xff1a;第 i 个泰波那契数的值。 2. 状态转移方程&#xff1a; …

Social to Sales全链路,数说故事专享会开启出海新视角

————瞎出海&#xff0c;必出局 TikTok&#xff0c;这个充满活力的短视频平台&#xff0c;已经成为全球范围内不可忽视的电商巨头。就在6月8日&#xff0c;TikTok美区带货直播诞生了首个“百万大场”。在此之前&#xff0c;百万GMV被视为一道难以逾越的高墙。以TikTok为首的…

Zabbix分布式监控

目录 分布式监控架构 实现分布式监控的步骤 优点和应用场景 安装Zabbix_Proxy Server端Web页面配置 测试 Zabbix 的分布式监控架构允许在大规模和地理上分散的环境中进行高效的监控。通过分布式监控&#xff0c;Zabbix 可以扩展其监控能力&#xff0c;支持大量主机和设备…

Android - 云游戏本地悬浮输入框实现

一、简述 云游戏输入法分两种情况,以云化原神为例,分为 云端输入法 和 本地输入法,运行效果如下: 云端输入法本地输入法云端输入法 就是运行在云端设备上的输入法,对于不同客户端来说(Android、iPhone),运行效果一致。 本地输入法 则是运行在用户侧设备上的输入法,对…

WordPress开发进群V2主题源码,多种引流方法,引私域二次变现

WordPress开发进群V2主题源码&#xff0c;多种引流方法&#xff0c;引私域二次变现 全新前端UI界面&#xff0c;多种前端交互特效让页面不再单调&#xff0c;进群页面群成员数&#xff0c;群成员头像名称&#xff0c;每次刷新页面随机更新不重复&#xff0c;最下面评论和点赞也…

C语言编程3:运算符,运算符的基本用法

C语言3&#x1f525;&#xff1a;运算符&#xff0c;运算符的基本用法 一、运算符&#x1f33f; &#x1f387;1.1 定义 运算符是指进行运算的动作&#xff0c;比如加法运算符"“&#xff0c;减法运算符”-" 算子是指参与运算的值&#xff0c;这个值可能是常数&a…

4.动态SQL(if,choose,where,set,trim,foreach遍历)的使用+$和#的区别

文章目录 动态sql一、动态sql1.if条件判断2、choose、when、otherwise3、where标签4、set标签5、trim标签1)替代where标签效果2) 生成set标签效果 6、foreach迭代遍历1)属性 7.SQL标签-提取重用的SQL代码片段8、bind标签9.MyBatis中${}和#{}的区别: 动态sql 一、动态sql 常见…

React -- useState状态更新异步特性——导致获取值为旧值的问题

useState状态异步更新 问题导致的原因解决办法进一步分析后续遇到的新问题 问题 const [isSelecting, setIsSelecting] useState(false);useEffect(() > {const handleKeyDown (event) > {if (event.key Escape) {if(isSelectingRef){//.......setIsSelecting(!isSele…

js使用proxy代理监听控制事件

本文为proxy代理的实例应用&#xff0c;有关代理的内容可以参考&#xff1a; js语法---理解反射Reflect对象和代理Proxy对象 监听事件 要监听dom元素的事件&#xff0c;我们会采用回调触发的方式来执行操作&#xff0c; 而触发事件的过程很明显是一个异步操作&#xff0c;异…

Oracle中EXIT Statement用于终止循环语句的关键字

Oracle的EXIT Statement是PL/SQL编程语言中用于终止循环语句的关键字。它有两种主要形式&#xff1a;无条件EXIT和条件EXIT WHEN。以下是对Oracle EXIT Statement的详细解释&#xff1a; 1. 无条件EXIT 语法&#xff1a;EXIT; 作用&#xff1a;无条件地终止当前循环。当程序执…