Files
vf_react/src/views/Company/components/DeepAnalysis/hooks/useDeepAnalysisData.ts
zdl ce4da40ef6 refactor(DeepAnalysis): TypeScript 重构,提取 useDeepAnalysisData Hook
- 新增 types.ts:API 类型定义、状态接口、Tab 映射常量
- 新增 hooks/useDeepAnalysisData.ts:提取数据获取逻辑
  - 懒加载:按 Tab 按需请求
  - 数据缓存:已加载数据不重复请求
  - 竞态处理:stockCode 变更时防止旧请求覆盖
- 重写 index.tsx:memo 优化,代码行数 229 → 81
- 新增 README.md:组件文档

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 13:42:16 +08:00

151 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* useDeepAnalysisData Hook
*
* 管理深度分析模块的数据获取逻辑:
* - 按 Tab 懒加载数据
* - 已加载数据缓存,避免重复请求
* - 竞态条件处理
*/
import { useState, useCallback, useRef, useEffect } from 'react';
import axios from '@utils/axiosConfig';
import { logger } from '@utils/logger';
import type {
ApiKey,
ApiLoadingState,
DataState,
UseDeepAnalysisDataReturn,
} from '../types';
import { TAB_API_MAP } from '../types';
/** API 端点映射 */
const API_ENDPOINTS: Record<ApiKey, string> = {
comprehensive: '/api/company/comprehensive-analysis',
valueChain: '/api/company/value-chain-analysis',
keyFactors: '/api/company/key-factors-timeline',
industryRank: '/api/financial/industry-rank',
};
/** 初始数据状态 */
const initialDataState: DataState = {
comprehensive: null,
valueChain: null,
keyFactors: null,
industryRank: null,
};
/** 初始 loading 状态 */
const initialLoadingState: ApiLoadingState = {
comprehensive: false,
valueChain: false,
keyFactors: false,
industryRank: false,
};
/**
* 深度分析数据 Hook
*
* @param stockCode 股票代码
* @returns 数据、loading 状态、加载函数
*/
export const useDeepAnalysisData = (stockCode: string): UseDeepAnalysisDataReturn => {
// 数据状态
const [data, setData] = useState<DataState>(initialDataState);
// Loading 状态
const [loading, setLoading] = useState<ApiLoadingState>(initialLoadingState);
// 已加载的接口记录
const loadedApisRef = useRef<Record<ApiKey, boolean>>({
comprehensive: false,
valueChain: false,
keyFactors: false,
industryRank: false,
});
// 当前 stockCode用于竞态条件检测
const currentStockCodeRef = useRef(stockCode);
/**
* 加载指定 API 数据
*/
const loadApiData = useCallback(
async (apiKey: ApiKey) => {
if (!stockCode) return;
// 已加载则跳过
if (loadedApisRef.current[apiKey]) return;
// 设置 loading
setLoading((prev) => ({ ...prev, [apiKey]: true }));
try {
const endpoint = `${API_ENDPOINTS[apiKey]}/${stockCode}`;
const { data: response } = await axios.get(endpoint);
// 检查 stockCode 是否已变更(防止竞态)
if (currentStockCodeRef.current !== stockCode) return;
if (response.success) {
setData((prev) => ({ ...prev, [apiKey]: response.data }));
loadedApisRef.current[apiKey] = true;
}
} catch (err) {
logger.error('DeepAnalysis', `loadApiData:${apiKey}`, err, { stockCode });
} finally {
// 清除 loading再次检查 stockCode
if (currentStockCodeRef.current === stockCode) {
setLoading((prev) => ({ ...prev, [apiKey]: false }));
}
}
},
[stockCode]
);
/**
* 根据 Tab 加载对应数据
*/
const loadTabData = useCallback(
(tabKey: string) => {
const apiKey = TAB_API_MAP[tabKey];
if (apiKey) {
loadApiData(apiKey);
}
},
[loadApiData]
);
/**
* 重置所有数据
*/
const resetData = useCallback(() => {
setData(initialDataState);
setLoading(initialLoadingState);
loadedApisRef.current = {
comprehensive: false,
valueChain: false,
keyFactors: false,
industryRank: false,
};
}, []);
// stockCode 变更时重置并加载默认数据
useEffect(() => {
if (stockCode) {
currentStockCodeRef.current = stockCode;
resetData();
// 只加载默认 Tabcomprehensive
loadApiData('comprehensive');
}
}, [stockCode, loadApiData, resetData]);
return {
data,
loading,
loadTabData,
resetData,
};
};
export default useDeepAnalysisData;