feat: 创建自定义 Hooks
This commit is contained in:
173
src/components/StockChart/hooks/useKLineChart.ts
Normal file
173
src/components/StockChart/hooks/useKLineChart.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user