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

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

时间:2024-07-08 09:57:54浏览次数:12  
标签:false -- TS React import 组件 return echarts const

文章目录


前言

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. 效果展示

在这里插入图片描述


总结

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

标签:false,--,TS,React,import,组件,return,echarts,const
From: https://blog.csdn.net/weixin_43883615/article/details/140212598

相关文章

  • Web前端工程师修炼之道
    代码和PDF等:GitHub-guozhe1992/readWeb设计基础:介绍Web设计的核心概念和基本原则,包括网页的构成元素、页面布局、色彩搭配等,帮助读者建立对Web设计的整体认识。HTML基础:详细讲解HTML(超文本标记语言)的基本语法和常用标签,以及如何使用HTML构建网页结构和内容。CSS样式设计:介......
  • Spring Boot3整合Mybatis Plus,数据库为MySQL
    项目结构如下:注意不需要任何XML文件1.导入依赖除了SpringBoot创建时自带的依赖,还需要加入:<!--MybatisPlus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.7</version&g......
  • 插入记录(二)(sql练习)
    现有一张试卷作答记录表exam_record,结构如下表,其中包含多年来的用户作答试卷记录,由于数据越来越多,维护难度越来越大,需要对数据表内容做精简,历史数据做备份。表exam_record:FiledTypeNullKeyExtraDefaultCommentidint(11)NOPRIauto_increment(NULL)自增IDuidint(11)NO(NULL)用......
  • SpringBoot整合Radis(redis启用,maven依赖,及具体实用)
    文章目录1、本地下载redis并且开启2、导入maven依赖3、添加application.properties4、创建配置类RedisConfig.java5、使用1、注解1、@Cacheable(value="",key="")2、**@CachePut**(value="",key="")3、CacheEvict(value="",key="")2、示例1、本地下......
  • CloudFront常用权限分类详解
    AmazonCloudFront是一个快速内容分发网络(CDN)服务,它可以安全地以低延迟和高传输速度向客户分发数据、视频、应用程序和API。为了确保CloudFront资源的安全性和访问控制,AWS提供了一套细粒度的权限管理机制。本文将详细介绍CloudFront的常用权限分类,并提供相应的JSON策略示例。1......
  • 数论知识(取模运算)
    若amodk=xa......
  • 数据挖掘小参考资料推荐
    原文链接博客github目录[判别式模型和生成式模型的区别|知乎](https://zhuanlan.zhihu.com/p/74586507)[Gini系数生成决策树|博客](https://victorzhou.com/blog/gini-impurity/)[一个学数据挖掘的开发者的博客|博客](https://victorzhou.com/page/5/)[信息增......
  • 介绍 Docker 的基本概念和优势,以及在应用程序开发中的实际应用。
    Docker是一种开源的容器化平台,用于构建、部署和管理应用程序。它采用了轻量级的虚拟化技术,允许将应用程序及其依赖包装在一个独立的容器中,以便于在不同的环境中运行。Docker的主要优势包括:1.轻量级和快速启动:Docker容器与传统虚拟化相比更轻量级,可以在几秒钟内启动和停止。......
  • 一起学Hugging Face Transformers(13)- 模型微调之自定义训练循环
    文章目录前言一、什么是训练循环1.训练循环的关键步骤2.示例3.训练循环的重要性二、使用HuggingFaceTransformers库实现自定义训练循环1.前期准备1)安装依赖2)导入必要的库2.加载数据和模型1)加载数据集2)加载预训练模型和分词器3)预处理数据4)创建数据加载器3......
  • 字典映射处理
       正常情况下不同的终端在进行接口对接时会对传输的数据进行加密,在解密之后拿到字符串,要么直接用动态类型获取,但是又要对字段进行判空校验,非常麻烦,这里封装了实体映射的方法,直接把解密后的字符串进行解析,映射到对应的实体对象中,其中是包括单实体,以及包含带子表的实体映射。......