fix(CompanyOverview): 修复 React Strict Mode 下骨架屏闪现问题
- 移除所有 hooks 中的 finally 块,避免请求取消时错误更新状态 - 添加 hasLoaded 状态追踪首次加载完成 - CanceledError 时直接返回,不更新任何状态 - 使用派生 isLoading 状态确保骨架屏正确显示 修复的 hooks: - useShareholderData.ts - useManagementData.ts - useAnnouncementsData.ts - useDisclosureData.ts - useBasicInfo.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,12 +37,16 @@ export const useAnnouncementsData = (options: UseAnnouncementsDataOptions): UseA
|
|||||||
const { stockCode, enabled = true, refreshKey } = options;
|
const { stockCode, enabled = true, refreshKey } = options;
|
||||||
|
|
||||||
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
|
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [hasLoaded, setHasLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有 enabled 且有 stockCode 时才请求
|
// 只有 enabled 且有 stockCode 时才请求
|
||||||
if (!enabled || !stockCode) return;
|
if (!enabled || !stockCode) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
@@ -61,12 +65,17 @@ export const useAnnouncementsData = (options: UseAnnouncementsDataOptions): UseA
|
|||||||
} else {
|
} else {
|
||||||
setError("加载公告数据失败");
|
setError("加载公告数据失败");
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === "CanceledError") return;
|
// 请求被取消时,不更新任何状态
|
||||||
|
if (err.name === "CanceledError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.error("useAnnouncementsData", "loadData", err, { stockCode });
|
logger.error("useAnnouncementsData", "loadData", err, { stockCode });
|
||||||
setError("网络请求失败");
|
setError("网络请求失败");
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,5 +83,7 @@ export const useAnnouncementsData = (options: UseAnnouncementsDataOptions): UseA
|
|||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, [stockCode, enabled, refreshKey]);
|
}, [stockCode, enabled, refreshKey]);
|
||||||
|
|
||||||
return { announcements, loading, error };
|
const isLoading = loading || (enabled && !hasLoaded && !error);
|
||||||
|
|
||||||
|
return { announcements, loading: isLoading, error };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,13 +34,16 @@ export const useBasicInfo = (options: UseBasicInfoOptions): UseBasicInfoResult =
|
|||||||
const { stockCode, enabled = true } = options;
|
const { stockCode, enabled = true } = options;
|
||||||
|
|
||||||
const [basicInfo, setBasicInfo] = useState<BasicInfo | null>(null);
|
const [basicInfo, setBasicInfo] = useState<BasicInfo | null>(null);
|
||||||
// 智能初始化 loading:当需要加载数据时,初始值为 true,避免首次渲染闪现空状态
|
const [loading, setLoading] = useState(true);
|
||||||
const [loading, setLoading] = useState(() => enabled && !!stockCode);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [hasLoaded, setHasLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有 enabled 且有 stockCode 时才请求
|
// 只有 enabled 且有 stockCode 时才请求
|
||||||
if (!enabled || !stockCode) return;
|
if (!enabled || !stockCode) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
@@ -59,12 +62,17 @@ export const useBasicInfo = (options: UseBasicInfoOptions): UseBasicInfoResult =
|
|||||||
} else {
|
} else {
|
||||||
setError("加载基本信息失败");
|
setError("加载基本信息失败");
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === "CanceledError") return;
|
// 请求被取消时,不更新任何状态
|
||||||
|
if (err.name === "CanceledError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.error("useBasicInfo", "loadData", err, { stockCode });
|
logger.error("useBasicInfo", "loadData", err, { stockCode });
|
||||||
setError("网络请求失败");
|
setError("网络请求失败");
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,5 +80,7 @@ export const useBasicInfo = (options: UseBasicInfoOptions): UseBasicInfoResult =
|
|||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, [stockCode, enabled]);
|
}, [stockCode, enabled]);
|
||||||
|
|
||||||
return { basicInfo, loading, error };
|
const isLoading = loading || (enabled && !hasLoaded && !error);
|
||||||
|
|
||||||
|
return { basicInfo, loading: isLoading, error };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,12 +34,16 @@ export const useDisclosureData = (options: UseDisclosureDataOptions): UseDisclos
|
|||||||
const { stockCode, enabled = true } = options;
|
const { stockCode, enabled = true } = options;
|
||||||
|
|
||||||
const [disclosureSchedule, setDisclosureSchedule] = useState<DisclosureSchedule[]>([]);
|
const [disclosureSchedule, setDisclosureSchedule] = useState<DisclosureSchedule[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [hasLoaded, setHasLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有 enabled 且有 stockCode 时才请求
|
// 只有 enabled 且有 stockCode 时才请求
|
||||||
if (!enabled || !stockCode) return;
|
if (!enabled || !stockCode) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
@@ -58,12 +62,17 @@ export const useDisclosureData = (options: UseDisclosureDataOptions): UseDisclos
|
|||||||
} else {
|
} else {
|
||||||
setError("加载披露日程数据失败");
|
setError("加载披露日程数据失败");
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === "CanceledError") return;
|
// 请求被取消时,不更新任何状态
|
||||||
|
if (err.name === "CanceledError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.error("useDisclosureData", "loadData", err, { stockCode });
|
logger.error("useDisclosureData", "loadData", err, { stockCode });
|
||||||
setError("网络请求失败");
|
setError("网络请求失败");
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,5 +80,7 @@ export const useDisclosureData = (options: UseDisclosureDataOptions): UseDisclos
|
|||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, [stockCode, enabled]);
|
}, [stockCode, enabled]);
|
||||||
|
|
||||||
return { disclosureSchedule, loading, error };
|
const isLoading = loading || (enabled && !hasLoaded && !error);
|
||||||
|
|
||||||
|
return { disclosureSchedule, loading: isLoading, error };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,14 +34,16 @@ export const useManagementData = (options: UseManagementDataOptions): UseManagem
|
|||||||
const { stockCode, enabled = true } = options;
|
const { stockCode, enabled = true } = options;
|
||||||
|
|
||||||
const [management, setManagement] = useState<Management[]>([]);
|
const [management, setManagement] = useState<Management[]>([]);
|
||||||
const [loading, setLoading] = useState(() => enabled && !!stockCode);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
// 记录是否已完成首次加载,用于派生 loading 状态
|
|
||||||
const [hasLoaded, setHasLoaded] = useState(false);
|
const [hasLoaded, setHasLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有 enabled 且有 stockCode 时才请求
|
// 只有 enabled 且有 stockCode 时才请求
|
||||||
if (!enabled || !stockCode) return;
|
if (!enabled || !stockCode) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
@@ -60,11 +62,15 @@ export const useManagementData = (options: UseManagementDataOptions): UseManagem
|
|||||||
} else {
|
} else {
|
||||||
setError("加载管理团队数据失败");
|
setError("加载管理团队数据失败");
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === "CanceledError") return;
|
// 请求被取消时,不更新任何状态
|
||||||
|
if (err.name === "CanceledError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.error("useManagementData", "loadData", err, { stockCode });
|
logger.error("useManagementData", "loadData", err, { stockCode });
|
||||||
setError("网络请求失败");
|
setError("网络请求失败");
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setHasLoaded(true);
|
setHasLoaded(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,16 @@ export const useShareholderData = (options: UseShareholderDataOptions): UseShare
|
|||||||
const [concentration, setConcentration] = useState<Concentration[]>([]);
|
const [concentration, setConcentration] = useState<Concentration[]>([]);
|
||||||
const [topShareholders, setTopShareholders] = useState<Shareholder[]>([]);
|
const [topShareholders, setTopShareholders] = useState<Shareholder[]>([]);
|
||||||
const [topCirculationShareholders, setTopCirculationShareholders] = useState<Shareholder[]>([]);
|
const [topCirculationShareholders, setTopCirculationShareholders] = useState<Shareholder[]>([]);
|
||||||
// 智能初始化 loading:当需要加载数据时,初始值为 true,避免首次渲染闪现空状态
|
const [loading, setLoading] = useState(true);
|
||||||
const [loading, setLoading] = useState(() => enabled && !!stockCode);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [hasLoaded, setHasLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有 enabled 且有 stockCode 时才请求
|
// 只有 enabled 且有 stockCode 时才请求
|
||||||
if (!enabled || !stockCode) return;
|
if (!enabled || !stockCode) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
@@ -71,12 +74,17 @@ export const useShareholderData = (options: UseShareholderDataOptions): UseShare
|
|||||||
if (concentrationRes.success) setConcentration(concentrationRes.data);
|
if (concentrationRes.success) setConcentration(concentrationRes.data);
|
||||||
if (shareholdersRes.success) setTopShareholders(shareholdersRes.data);
|
if (shareholdersRes.success) setTopShareholders(shareholdersRes.data);
|
||||||
if (circulationRes.success) setTopCirculationShareholders(circulationRes.data);
|
if (circulationRes.success) setTopCirculationShareholders(circulationRes.data);
|
||||||
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === "CanceledError") return;
|
// 请求被取消时,不更新任何状态
|
||||||
|
if (err.name === "CanceledError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.error("useShareholderData", "loadData", err, { stockCode });
|
logger.error("useShareholderData", "loadData", err, { stockCode });
|
||||||
setError("加载股权结构数据失败");
|
setError("加载股权结构数据失败");
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setHasLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,12 +92,14 @@ export const useShareholderData = (options: UseShareholderDataOptions): UseShare
|
|||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, [stockCode, enabled]);
|
}, [stockCode, enabled]);
|
||||||
|
|
||||||
|
const isLoading = loading || (enabled && !hasLoaded && !error);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actualControl,
|
actualControl,
|
||||||
concentration,
|
concentration,
|
||||||
topShareholders,
|
topShareholders,
|
||||||
topCirculationShareholders,
|
topCirculationShareholders,
|
||||||
loading,
|
loading: isLoading,
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user