feat: 创建自定义 Hooks

This commit is contained in:
zdl
2025-11-22 23:14:16 +08:00
parent c391c4c980
commit bcd67ed410
4 changed files with 619 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
/**
* useKLineChart Hook
*
* 管理 KLineChart 实例的初始化、配置和销毁
*/
import { useEffect, useRef, useState } from 'react';
import { init, dispose } from 'klinecharts';
import type { Chart } from 'klinecharts';
import { useColorMode } from '@chakra-ui/react';
import { getTheme } from '../config/klineTheme';
import { CHART_INIT_OPTIONS } from '../config';
import { logger } from '@utils/logger';
export interface UseKLineChartOptions {
/** 图表容器 ID */
containerId: string;
/** 图表高度px */
height?: number;
/** 是否自动调整大小 */
autoResize?: boolean;
}
export interface UseKLineChartReturn {
/** KLineChart 实例 */
chart: Chart | null;
/** 容器 Ref */
chartRef: React.RefObject<HTMLDivElement>;
/** 是否已初始化 */
isInitialized: boolean;
/** 初始化错误 */
error: Error | null;
}
/**
* KLineChart 初始化和生命周期管理 Hook
*
* @param options 配置选项
* @returns UseKLineChartReturn
*
* @example
* const { chart, chartRef, isInitialized } = useKLineChart({
* containerId: 'kline-chart',
* height: 400,
* autoResize: true,
* });
*/
export const useKLineChart = (
options: UseKLineChartOptions
): UseKLineChartReturn => {
const { containerId, height = 400, autoResize = true } = options;
const chartRef = useRef<HTMLDivElement>(null);
const chartInstanceRef = useRef<Chart | null>(null);
const [isInitialized, setIsInitialized] = useState(false);
const [error, setError] = useState<Error | null>(null);
const { colorMode } = useColorMode();
// 图表初始化
useEffect(() => {
if (!chartRef.current) {
logger.warn('useKLineChart', 'init', '图表容器未挂载', { containerId });
return;
}
try {
logger.debug('useKLineChart', 'init', '开始初始化图表', {
containerId,
height,
colorMode,
});
// 初始化图表实例KLineChart 10.0 API
const chartInstance = init(chartRef.current, {
...CHART_INIT_OPTIONS,
// 设置初始样式(根据主题)
styles: getTheme(colorMode),
});
if (!chartInstance) {
throw new Error('图表初始化失败:返回 null');
}
chartInstanceRef.current = chartInstance;
setIsInitialized(true);
setError(null);
logger.info('useKLineChart', 'init', '图表初始化成功', {
containerId,
chartId: chartInstance.id,
});
} catch (err) {
const error = err as Error;
logger.error('useKLineChart', 'init', error, { containerId });
setError(error);
setIsInitialized(false);
}
// 清理函数:销毁图表实例
return () => {
if (chartInstanceRef.current) {
logger.debug('useKLineChart', 'dispose', '销毁图表实例', {
containerId,
chartId: chartInstanceRef.current.id,
});
dispose(chartInstanceRef.current);
chartInstanceRef.current = null;
setIsInitialized(false);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [containerId]); // 只在 containerId 变化时重新初始化
// 主题切换:更新图表样式
useEffect(() => {
if (!chartInstanceRef.current || !isInitialized) {
return;
}
try {
const newTheme = getTheme(colorMode);
chartInstanceRef.current.setStyles(newTheme);
logger.debug('useKLineChart', 'updateTheme', '更新图表主题', {
colorMode,
chartId: chartInstanceRef.current.id,
});
} catch (err) {
logger.error('useKLineChart', 'updateTheme', err as Error, { colorMode });
}
}, [colorMode, isInitialized]);
// 容器尺寸变化:调整图表大小
useEffect(() => {
if (!chartInstanceRef.current || !isInitialized || !autoResize) {
return;
}
const handleResize = () => {
if (chartInstanceRef.current) {
chartInstanceRef.current.resize();
logger.debug('useKLineChart', 'resize', '调整图表大小');
}
};
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
// 使用 ResizeObserver 监听容器大小变化(更精确)
let resizeObserver: ResizeObserver | null = null;
if (chartRef.current && typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(chartRef.current);
}
return () => {
window.removeEventListener('resize', handleResize);
if (resizeObserver && chartRef.current) {
resizeObserver.unobserve(chartRef.current);
resizeObserver.disconnect();
}
};
}, [isInitialized, autoResize]);
return {
chart: chartInstanceRef.current,
chartRef,
isInitialized,
error,
};
};