Bootstrap

React+TS前台项目实战(二十九)-- 用useQuery和React Hook优化实现首页Echarts模块数据渲染


前言

还记得之前我们创建的 高性能可配置Echarts组件 吗?今天我们将利用它来呈现首页统计模块的数据可视化效果,借助这个组件,我们能够显著减少编写代码的工作量,会方便很多。

Echart模块源码+功能分析+数据渲染

一、HashRateEchart统计图

1. 功能分析

(1)数据获取:使用@tanstack/react-query库来处理数据获取。使用useQuery请求并缓存数据
(2)组件缓存:使用memo高阶组件进行缓存。有助于提高性能,防止不必要的重新渲染组件
(3)数据处理:使用useMemo钩子缓存处理后的图表数据(fullEchartData和echartData),确保只有在必要时才进行数据处理,从而减少不必要的计算
(4)懒加载渲染劫持:组件根据数据的可用性进行条件渲染,数据加载中时显示Loading组件
(5)引用公共组件:使用Echart公共组件,提高开发效率,组件可看之前文章 高性能可配置Echarts图表组件封装

2. 代码+详细注释

// @/components/Home/HashRateEchart/index.tsx
import { memo, useMemo } from "react";
import BigNumber from "bignumber.js";
import { HomeChartBlock, ChartLoadingBlock } from "./styled";
import classNames from "classnames";
import "echarts/lib/chart/line";
import "echarts/lib/component/title";
import echarts from "echarts/lib/echarts";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import Loading from "@/components/Loading";
import { ReactChartBlock } from "@/components/Echarts/common";
import { queryStatisticHashRate } from '@/api/home'
// echarts 配置
const useOption = () => {
  const { t } = useTranslation();
  return (data: any, useMiniStyle: boolean): echarts.EChartOption => {
    return {
      color: ["#ffffff"],
      title: {
        text: "平均出块时间(s)",
        textAlign: "left",
        textStyle: {
          color: "#ffffff",
          fontSize: 14,
          fontWeight: "lighter",
          fontFamily: "Lato",
        },
      },
      grid: {
        left: useMiniStyle ? "1%" : "2%",
        right: "3%",
        top: useMiniStyle ? "20%" : "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()),
        },
      ],
    };
  };
};
// 使用memo钩子函数提升性能
export default memo(() => {
  // 使用useQuery请求数据
  const query = useQuery(["StatisticHashRate"], async () => {
    const { data,total } = await queryStatisticHashRate({
      page: 1,
      page_size: 25,
    });
    return {
      data,
      total: total ?? data?.length,
    };
  }, {
    refetchOnWindowFocus: false,
  });
  // 处理数据,并通过useMemo实现数据的缓存
  const fullEchartData = useMemo(() => query.data ?? [], [query.data]);
  // 获取最近14天的数据,并通过useMemo实现数据的缓存
  const echartData = useMemo(() => {
    const last14Days = -15;
    return fullEchartData.slice(last14Days);
  }, [fullEchartData]);
  // 根据数据渲染图表,当数据为空时显示没有数据,正在请求数据时显示加载中
  if (query.isLoading || !echartData?.length) {
    return <ChartLoadingBlock>{query.isLoading ? <Loading size="small" /> : <div className={classNames("no-data")}>暂无数据</div>}</ChartLoadingBlock>;
  }
  // 获取echarts的option配置
  const parseOption = useOption();
  return (
    <HomeChartBlock to="/block-list">
      {/* 使用公共Echart组件 */}
      <ReactChartBlock
        option={parseOption(echartData, true)}
        notMerge
        lazyUpdate
        style={{
          height: "180px",
        }}
      ></ReactChartBlock>
    </HomeChartBlock>
  );
});
--------------------------------------------------------------------------
import styled from "styled-components";
import Link from "@/components/Link";
export const HomeChartBlock = styled(Link)`
  canvas {
    cursor: pointer;
  }
`;
export const ChartLoadingBlock = styled.div`
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  .no-data {
    font-size: 18px;
  }
`;

二、BlockTimeChart统计图

1. 功能分析

注:此处忽略,功能和上面HashRateEchart统计表基本一致,只是数据请求不同

2. 代码+详细注释

// @/components/Home/BlockTimeChart/index.tsx
import { memo, useMemo } from "react";
import BigNumber from "bignumber.js";
import { HomeChartBlock, ChartLoadingBlock } from "./styled";
import classNames from "classnames";
import "echarts/lib/chart/line";
import "echarts/lib/component/title";
import echarts from "echarts/lib/echarts";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import Loading from "@/components/Loading";
import { ReactChartBlock } from "@/components/Echarts/common";
import { queryStatisticAverageBlockTimes } from '@/api/home'
// echarts 配置
const useOption = () => {
  const { t } = useTranslation();
  return (data: any, useMiniStyle: boolean): echarts.EChartOption => {
    return {
      color: ["#ffffff"],
      title: {
        text: "哈希率(H/s)",
        textAlign: "left",
        textStyle: {
          color: "#ffffff",
          fontSize: 14,
          fontWeight: "lighter",
          fontFamily: "Lato",
        },
      },
      grid: {
        left: useMiniStyle ? "1%" : "2%",
        right: "3%",
        top: useMiniStyle ? "20%" : "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()),
        },
      ],
    };
  };
};
// 使用memo钩子函数提升性能
export default memo(() => {
  // 使用useQuery请求数据
  const query = useQuery(["StatisticAverageBlockTimes"], async () => {
    const { data,total } = await queryStatisticAverageBlockTimes({
      page: 1,
      page_size: 25,
    });
    return {
      data,
      total: total ?? data?.length,
    };
  }, {
    refetchOnWindowFocus: false,
  });
  // 处理数据,并通过useMemo实现数据的缓存
  const fullEchartData = useMemo(() => query.data ?? [], [query.data]);
  // 获取最近14天的数据,并通过useMemo实现数据的缓存
  const echartData = useMemo(() => {
    const last14Days = -15;
    return fullEchartData.slice(last14Days);
  }, [fullEchartData]);
  // 根据数据渲染图表,当数据为空时显示没有数据,正在请求数据时显示加载中
  if (query.isLoading || !echartData?.length) {
    return <ChartLoadingBlock>{query.isLoading ? <Loading size="small" /> : <div className={classNames("no-data")}>暂无数据</div>}</ChartLoadingBlock>;
  }
  // 获取echarts的option配置
  const parseOption = useOption();
  return (
    <HomeChartBlock to="/block-list">
      {/* 使用公共Echart组件 */}
      <ReactChartBlock
        option={parseOption(echartData, true)}
        notMerge
        lazyUpdate
        style={{
          height: "180px",
        }}
      ></ReactChartBlock>
    </HomeChartBlock>
  );
});
-------------------------------------------------------------------------------------------------------
// @/components/Home/BlockTimeChart/styled.tsx
import styled from "styled-components";
import Link from "@/components/Link";
export const HomeChartBlock = styled(Link)`
  canvas {
    cursor: pointer;
  }
`;
export const ChartLoadingBlock = styled.div`
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  .no-data {
    font-size: 18px;
  }
`;

三、使用方式

结合首页响应式构建之banner、搜索、统计模块布局 这一讲,在统计模块中引入出块统计图表,以及挖矿统计图表即可

// 引入组件和echarts
import HashRateEchart from "./HashRateEchart/index";
import BlockTimeChart from "./BlockTimeChart/index";
// 使用
// ....
<HashRateEchart />
// ....
<BlockTimeChart />
// ....

四. 数据渲染后效果如下

(1)PC端

在这里插入图片描述
(2)移动端

在这里插入图片描述


总结

下一篇讲【首页响应式构建之实现全页面数据】。关注本栏目,将实时更新。

;