Преглед изворни кода

♻️ refactor(charts): 重构 BaseChartOriginal2D 组件结构并优化性能

- 将原组件拆分为外层 Wrapper 组件 `BaseChartOriginal2D` 和内部渲染组件 `BaseChartOriginal2DInner`
- 外层组件负责数据准备检查和配置缓存,仅在数据就绪时挂载内部组件
- 内部组件使用空依赖数组的 `useLayoutEffect` 确保图表只初始化一次
- 为图表初始化添加 500ms 延迟,确保 Canvas 尺寸渲染完成
- 使用 `useMemo` 缓存配置对象,避免不必要的重新创建

✨ feat(charts): 为统计页面残疾类型图表添加数据缓存

- 使用 `useMemo` 缓存残疾类型图表的转换数据
- 简化渲染逻辑,移除内联函数,提升代码可读性

📝 docs(charts): 更新组件注释和导出配置

- 更新组件注释,明确内外层组件的职责
- 清理 `components/index.ts` 导出文件,暂时不导出任何组件
yourname пре 2 недеља
родитељ
комит
35ca4953e7

+ 121 - 64
mini-ui-packages/mini-charts/src/components/BaseChartOriginal2D.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useRef, useLayoutEffect } from 'react';
+import React, { useState, useRef, useLayoutEffect, useMemo } from 'react';
 import Taro from '@tarojs/taro';
 import { Canvas } from '@tarojs/components';
 import uChartsClass from '../lib/u-charts-original.js';
@@ -33,12 +33,12 @@ export interface BaseChartOriginal2DProps {
 }
 
 /**
- * BaseChartOriginal2D 组件
+ * BaseChartOriginal2DInner 内部组件
  *
- * 使用原始 u-charts.js + Canvas 2D API
- * 参考 docs/小程序图表库示例/taro-2d柱状图使用示例.md
+ * 实际的图表渲染组件,只在数据准备好后才会挂载
+ * 使用空依赖数组确保只初始化一次
  */
-export const BaseChartOriginal2D: React.FC<BaseChartOriginal2DProps> = (props) => {
+const BaseChartOriginal2DInner: React.FC<BaseChartOriginal2DProps> = (props) => {
   const {
     canvasId,
     width = 750,
@@ -61,6 +61,9 @@ export const BaseChartOriginal2D: React.FC<BaseChartOriginal2DProps> = (props) =
    * 初始化图表实例
    * 使用 Canvas 2D API + 原始 u-charts.js
    * 参考 ColumnChartFCExample 的实现方式
+   *
+   * 注意:使用空依赖数组,只在组件首次挂载时执行一次
+   * 数据变化通过 Wrapper 组件控制重新挂载来实现
    */
   useLayoutEffect(() => {
     console.debug('[BaseChartOriginal2D] useLayoutEffect 开始', { canvasId, width, height });
@@ -87,65 +90,68 @@ export const BaseChartOriginal2D: React.FC<BaseChartOriginal2DProps> = (props) =
       return;
     }
 
-    // 使用 Canvas 2D API 的方式获取 context
-    const query = Taro.createSelectorQuery();
-    query.select('#' + canvasId).fields({ node: true, size: true }).exec((res) => {
-      if (res[0]) {
-        const canvas = res[0].node;
-        const ctx = canvas.getContext('2d');
-
-        console.debug('[BaseChartOriginal2D] canvas.width', canvas.width);
-        console.debug('[BaseChartOriginal2D] canvas.height', canvas.height);
-
-        // 将 Taro CanvasContext 转换为 uCharts 需要的 CanvasContext
-        const extendedCtx = ctx as ExtendedCanvasContext;
-
-        // Canvas 2D: 使用 canvas 的实际 width/height
-        const chartConfig: ChartsConfig = {
-          type,
-          context: extendedCtx,
-          categories,
-          series,
-          width: canvas.width,
-          height: canvas.height,
-          animation: true,
-          background: '#FFFFFF',
-          color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
-          padding: [15, 15, 0, 5],
-          enableScroll: false,
-          legend: {},
-          xAxis: {
-            disableGrid: true
-          },
-          yAxis: {
-            data: [{ min: 0 }]
-          },
-          extra: {
-            column: {
-              type: 'group',
-              width: 30,
-              activeBgColor: '#000000',
-              activeBgOpacity: 0.08
-            }
-          },
-          ...config,
-        };
-
-        chartRef.current = new uChartsClass(chartConfig);
-        console.log('[BaseChartOriginal2D] 图表初始化完成:', canvasId, {
-          cWidth, cHeight,
-          canvasWidth: canvas.width,
-          canvasHeight: canvas.height,
-          categoriesLength: categories.length,
-          seriesLength: series.length,
-          categories,
-          series,
-        });
-      } else {
-        console.error('[BaseChartOriginal2D] 未获取到 canvas node:', canvasId);
-      }
-    });
-  }, [canvasId, width, height, categories, series, config]);
+    // 延迟初始化图表,等待 Canvas 尺寸渲染完成(参考 FC 示例)
+    setTimeout(() => {
+      // 使用 Canvas 2D API 的方式获取 context
+      const query = Taro.createSelectorQuery();
+      query.select('#' + canvasId).fields({ node: true, size: true }).exec((res) => {
+        if (res[0]) {
+          const canvas = res[0].node;
+          const ctx = canvas.getContext('2d');
+
+          console.debug('[BaseChartOriginal2D] canvas.width', canvas.width);
+          console.debug('[BaseChartOriginal2D] canvas.height', canvas.height);
+
+          // 将 Taro CanvasContext 转换为 uCharts 需要的 CanvasContext
+          const extendedCtx = ctx as ExtendedCanvasContext;
+
+          // Canvas 2D: 使用 canvas 的实际 width/height
+          const chartConfig: ChartsConfig = {
+            type,
+            context: extendedCtx,
+            categories,
+            series,
+            width: canvas.width,
+            height: canvas.height,
+            animation: true,
+            background: '#FFFFFF',
+            color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
+            padding: [15, 15, 0, 5],
+            enableScroll: false,
+            legend: {},
+            xAxis: {
+              disableGrid: true
+            },
+            yAxis: {
+              data: [{ min: 0 }]
+            },
+            extra: {
+              column: {
+                type: 'group',
+                width: 30,
+                activeBgColor: '#000000',
+                activeBgOpacity: 0.08
+              }
+            },
+            ...config,
+          };
+
+          chartRef.current = new uChartsClass(chartConfig);
+          console.log('[BaseChartOriginal2D] 图表初始化完成:', canvasId, {
+            cWidth, cHeight,
+            canvasWidth: canvas.width,
+            canvasHeight: canvas.height,
+            categoriesLength: categories.length,
+            seriesLength: series.length,
+            categories,
+            series,
+          });
+        } else {
+          console.error('[BaseChartOriginal2D] 未获取到 canvas node:', canvasId);
+        }
+      });
+    }, 500); // 延迟 500ms,等待 Canvas 尺寸渲染完成
+  }, []); // 空依赖数组:只在首次挂载时执行一次
 
   /**
    * 触摸事件处理
@@ -188,4 +194,55 @@ export const BaseChartOriginal2D: React.FC<BaseChartOriginal2DProps> = (props) =
   );
 };
 
+/**
+ * BaseChartOriginal2D 组件
+ *
+ * 外层 Wrapper 组件,负责:
+ * - 检查数据是否准备好
+ * - 缓存 config 引用
+ * - 只有数据准备好后才挂载 Inner 组件
+ *
+ * 使用原始 u-charts.js + Canvas 2D API
+ * 参考 docs/小程序图表库示例/taro-2d柱状图使用示例.md
+ */
+export const BaseChartOriginal2D: React.FC<BaseChartOriginal2DProps> = (props) => {
+  const {
+    canvasId,
+    width,
+    height,
+    type,
+    categories = [],
+    series = [],
+    config = {},
+    onTouchStart,
+    onTouchMove,
+    onTouchEnd,
+  } = props;
+
+  // 缓存配置,避免每次渲染创建新对象
+  const stableConfig = useMemo(() => config, [JSON.stringify(config)]);
+
+  // 只有数据准备好才渲染 Inner 组件
+  const isReady = categories.length > 0 && series.length > 0;
+
+  if (!isReady) {
+    return null;
+  }
+
+  return (
+    <BaseChartOriginal2DInner
+      canvasId={canvasId}
+      width={width}
+      height={height}
+      type={type}
+      categories={categories}
+      series={series}
+      config={stableConfig}
+      onTouchStart={onTouchStart}
+      onTouchMove={onTouchMove}
+      onTouchEnd={onTouchEnd}
+    />
+  );
+};
+
 export default BaseChartOriginal2D;

+ 1 - 28
mini-ui-packages/mini-charts/src/components/index.ts

@@ -1,28 +1 @@
-// // BaseChart component
-// export { BaseChart, default as BaseChartDefault } from './BaseChart';
-// export type { BaseChartProps } from './BaseChart';
-
-// // BarChart component (横向柱状图)
-// export { BarChart, default as BarChartDefault } from './BarChart';
-// export type { BarChartProps, BarType } from './BarChart';
-
-// // ColumnChart component
-// export { ColumnChart, default as ColumnChartDefault } from './ColumnChart';
-// export type { ColumnChartProps, ColumnType } from './ColumnChart';
-
-// // LineChart component
-// export { LineChart, default as LineChartDefault } from './LineChart';
-// export type { LineChartProps, DataPointShape } from './LineChart';
-
-// // CandleChart component
-// export { CandleChart, default as CandleChartDefault } from './CandleChart';
-// export type { CandleChartProps } from './CandleChart';
-
-// // PieChart component
-// export { PieChart, default as PieChartDefault } from './PieChart';
-// export type { PieChartProps, PieChartType } from './PieChart';
-
-// // RadarChart component
-// export { RadarChart, default as RadarChartDefault } from './RadarChart';
-// export type { RadarChartProps } from './RadarChart';
-
+// 暂时不导出任何组件

+ 19 - 23
mini-ui-packages/yongren-statistics-ui/src/pages/Statistics/Statistics.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useEffect, memo } from 'react'
+import React, { useState, useEffect, memo, useMemo } from 'react'
 import { View, Text, ScrollView, Picker } from '@tarojs/components'
 import { useQuery } from '@tanstack/react-query'
 import { YongrenTabBarLayout } from '@d8d/yongren-shared-ui/components/YongrenTabBarLayout'
@@ -115,6 +115,12 @@ const Statistics: React.FC<StatisticsProps> = () => {
     gcTime: 10 * 60 * 1000 // 缓存时间10分钟
   })
 
+  // 缓存残疾类型图表数据,避免每次渲染创建新对象
+  const disabilityChartData = useMemo(() => {
+    const stats = getStats(disabilityData)
+    return stats.length > 0 ? convertToColumnData(stats) : null
+  }, [disabilityData])
+
   // 获取性别分布数据
   const { data: genderData, isLoading: isLoadingGender } = useQuery({
     queryKey: ['statistics', 'gender-distribution'],
@@ -297,29 +303,19 @@ const Statistics: React.FC<StatisticsProps> = () => {
           <Text className="font-semibold text-gray-700">残疾类型分布 (Canvas 2D + 原始JS)</Text>
           {isLoadingDisability ? (
             <Text className="text-gray-500 text-center py-4">加载中...</Text>
+          ) : disabilityChartData ? (
+            <View className="mt-3">
+              <ColumnChartOriginal2D
+                canvasId="disability-type-chart-orig2d"
+                width={650}
+                height={300}
+                categories={disabilityChartData.categories}
+                series={disabilityChartData.series}
+                config={{ color: ['#3b82f6'] }}
+              />
+            </View>
           ) : (
-            (() => {
-              const stats = getStats(disabilityData)
-              if (stats.length > 0) {
-                const chartData = convertToColumnData(stats)
-                return (
-                  <View className="mt-3">
-                    <ColumnChartOriginal2D
-                      canvasId="disability-type-chart-orig2d"
-                      width={650}
-                      height={300}
-                      categories={chartData.categories}
-                      series={chartData.series}
-                      config={{
-                        color: ['#3b82f6']
-                      }}
-                    />
-                  </View>
-                )
-              } else {
-                return <Text className="text-gray-500 text-center py-4 mt-3">暂无数据</Text>
-              }
-            })()
+            <Text className="text-gray-500 text-center py-4 mt-3">暂无数据</Text>
           )}
         </View>)}