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

news2025/1/11 6:56:34

文章目录

  • 前言
  • 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.coloradmin.cn/o/1912260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浏览器开发者视角及CSS表达式选择元素

点击想要查看的接口&#xff0c;然后点击检查&#xff0c;便可以切换到该接口对应的html代码 如果F12不起作用的话&#xff0c;点击更多工具&#xff0c;然后选择开发者工具即可 ctrlF可以去查阅相关的CSS表达式选择元素 如果没有加#t1&#xff0c;那么表示的是选择所有的p 使用…

对比学习和多模态任务

1. 对比学习 对比学习&#xff08;Contrastive Learning&#xff09;是一种自监督学习的方法&#xff0c;旨在通过比较数据表示空间中的不同样本来学习有用的特征表示。其核心思想是通过最大化同类样本之间的相似性&#xff08;或降低它们之间的距离&#xff09;&#xff0c;同…

使用F1C200S从零制作掌机之debian文件系统完善NES

一、模拟器源码 源码&#xff1a;https://files.cnblogs.com/files/twzy/arm-NES-linux-master.zip 二、文件系统 文件系统&#xff1a;debian bullseye 使用builtroot2018构建的文件系统&#xff0c;使用InfoNES模拟器存在bug&#xff0c;搞不定&#xff0c;所以放弃&…

这8款宝藏软件,才是安卓手机必装App!

​AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 1.我的日记——My Diary My Diary 是一款带锁的免费安卓日记工具。 它可用于记录每日日记、秘密想法、旅程、心情追踪或任何私人时刻。 你可…

使用AI学习英语

使用AI学英语可以通过与智能AI对话、模拟对话场景、提供即时反馈和个性化学习计划等方式提高学习效率和效果。然而&#xff0c;AI技术也存在局限性&#xff0c;如缺乏情感交流和真实语境&#xff0c;需要与真人教师结合使用。 AI学英语的基本原理和应用 AI的基本原理 AI&…

Java内存区域与内存溢出异常(补充)

2.2.5 方法区 方法区(Method Area)与Java堆一样&#xff0c;是各个线程共享的内存区域&#xff0c;它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分&#xff0c;但是它却有一…

设计模式探索:适配器模式

1. 适配器模式介绍 1.1 适配器模式介绍 适配器模式&#xff08;adapter pattern&#xff09;的原始定义是&#xff1a;将一个类的接口转换为客户期望的另一个接口&#xff0c;适配器可以让不兼容的两个类一起协同工作。 适配器模式的主要作用是把原本不兼容的接口&#xff0c…

采用3种稀疏降噪模型对心电信号进行降噪(Matlab R2021B)

心电信号采集自病人体表&#xff0c;是一种无创性的检测手段。因此&#xff0c;心电信号采集过程中&#xff0c;本身也已经包含了机体内部其他生命活动带来的噪声。同时&#xff0c;由于采集设备和环境中存在电流的变化&#xff0c;产生电磁发射等物理现象&#xff0c;会对心电…

3-6 构建线性模型解决温度计示数转换问题

3-6 构建线性模型解决温度计示数转换问题 直接上源码 %matplotlib inline import numpy as np import torch torch.set_printoptions(edgeitems2, linewidth75)导入必要的库并设置 PyTorch 的打印选项&#xff0c;确保在打印张量时显示边缘项和行宽。 #%% t_c [0.5, 14.0,…

【Android应用】生成证书和打包

安卓生成证书和打包 &#x1f4d6;1. 生成自有证书&#x1f4d6;2. 安卓打包✅步骤一&#xff1a;导入签名文件✅步骤二&#xff1a;设置打包版本✅步骤三&#xff1a;生成签名包或APK &#x1f4d6;1. 生成自有证书 地址&#xff1a;https://www.yunedit.com/createcert 说明…

C语言编译报错error: expected specifier-qualifier-list before

C语言编译报错 error: storage class specified for parameter error: expected specifier-qualifier-list before 原因&#xff1a; 报错信息 "expected specifier-qualifier-list" 通常表示编译器期望在某个地方出现类型指定列表&#xff0c;但却没有找到。这通常…

【目标检测】使用自己的数据集训练并预测yolov8模型

1、下载yolov8的官方代码 地址&#xff1a; GitHub - ultralytics/ultralytics: NEW - YOLOv8 &#x1f680; in PyTorch > ONNX > OpenVINO > CoreML > TFLite 2、下载目标检测的训练权重 yolov8n.pt 将 yolov8n.pt 放在ultralytics文件夹下 3、数据集分布 注…

【嵌入式DIY实例-ESP8266篇】-LCD ST7735显示BME280传感器数据

LCD ST7735显示BME280传感器数据 文章目录 LCD ST7735显示BME280传感器数据1、硬件准备与接线2、代码实现本文中将介绍如何使用 ESP8266 NodeMCU 板(ESP12-E 模块)和 BME280 气压、温度和湿度传感器构建气象站。 NodeMCU 微控制器 (ESP8266EX) 从 BME280 传感器读取温度、湿度…

2020 ICPC Shanghai Site B. Mine Sweeper II 题解 构造 鸽巢原理

Mine Sweeper II 题目描述 A mine-sweeper map X X X can be expressed as an n m n\times m nm grid. Each cell of the grid is either a mine cell or a non-mine cell. A mine cell has no number on it. Each non-mine cell has a number representing the number of…

大数据------JavaWeb------FilterListenerAJAXAxiosJSON

Filter Filter简介 定义&#xff1a;Filter表示过滤器&#xff0c;是JavaWeb三大组件&#xff08;Servlet、Filter、Listener&#xff09;之一。 作用&#xff1a;它可把对资源&#xff08;Servlet、JSP、Html&#xff09;的请求拦截下来从而实现一些特殊功能 过滤器一般完成…

Spring系统学习 - AOP之基于注解的AOP和XML的AOP

上一篇我们围绕了AOP中代理模式的使用&#xff0c;这篇我们将主要围绕AOP的相关术语介绍&#xff0c;以及重点围绕基于注解的AOP进行相关知识的概述和使用说明。 AOP的相关术语 切面&#xff08;Aspect&#xff09;&#xff1a;切面是一个模块化的横切关注点&#xff0c;它包含…

【关于车载测试的基础知识的认知详解】

目录 一、目前车企的趋势 1. 电动化&#xff1a; 2. 自动驾驶技术&#xff1a; 3. 车联网&#xff08;Connected Cars&#xff09;&#xff1a; 4. 智能化和数字化&#xff1a; 5. 安全性&#xff1a; 6. 轻量化&#xff1a; 7. 个性化和定制化&#xff1a; 8. 供应链…

C基础day7

一、思维导图 二、课后练习 1、提示并输入一个字符串&#xff0c;统计该字符串中字母、数字、空格以及其他字符的个数 #include<myhead.h> #define M 20 int main(int argc, const char *argv[]) {int sum_a0,sum_b0,sum_c0,sum_d0;char str[M];printf("please en…

Vagrant配合VirtualBox搭建虚拟机

目录 前言一、软件下载及安装1.下载2.安装扩展&#xff1a; 二、创建一个虚拟机1.Vagrant官方镜像仓库 三、使用远程工具连接虚拟机1.修改相关配置文件 四、虚拟机克隆及使用1.通用配置2.简单搭建一个java环境3.克隆虚拟机1.重命名虚拟机&#xff08;可选&#xff09;2.打包指定…

阿里发布大模型发布图结构长文本处理智能体,超越GPT-4-128k

随着大语言模型的发展&#xff0c;处理长文本的能力成为了一个重要挑战。虽然有许多方法试图解决这个问题&#xff0c;但都存在不同程度的局限性。最近&#xff0c;阿里巴巴的研究团队提出了一个名为GraphReader的新方法&#xff0c;通过将长文本组织成图结构&#xff0c;并利用…