pref: 优化 useEffect 依赖和清理逻辑
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
// src/views/Community/components/DynamicNewsCard.js
|
// src/views/Community/components/DynamicNewsCard.js
|
||||||
// 横向滚动事件卡片组件(实时要闻·动态追踪)
|
// 横向滚动事件卡片组件(实时要闻·动态追踪)
|
||||||
|
|
||||||
import React, { forwardRef, useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { forwardRef, useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -72,6 +72,9 @@ const DynamicNewsCard = forwardRef(({
|
|||||||
// 本地状态
|
// 本地状态
|
||||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||||
|
|
||||||
|
// 初始化标记 - 确保初始加载只执行一次
|
||||||
|
const hasInitialized = useRef(false);
|
||||||
|
|
||||||
// 使用分页 Hook
|
// 使用分页 Hook
|
||||||
const {
|
const {
|
||||||
currentPage,
|
currentPage,
|
||||||
@@ -91,9 +94,10 @@ const DynamicNewsCard = forwardRef(({
|
|||||||
toast
|
toast
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始加载
|
// 初始加载 - 只在组件首次挂载且未初始化时执行
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (allCachedEvents.length === 0) {
|
if (!hasInitialized.current && allCachedEvents.length === 0) {
|
||||||
|
hasInitialized.current = true;
|
||||||
dispatch(fetchDynamicNews({
|
dispatch(fetchDynamicNews({
|
||||||
page: PAGINATION_CONFIG.INITIAL_PAGE,
|
page: PAGINATION_CONFIG.INITIAL_PAGE,
|
||||||
per_page: PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE,
|
per_page: PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE,
|
||||||
@@ -103,12 +107,27 @@ const DynamicNewsCard = forwardRef(({
|
|||||||
}
|
}
|
||||||
}, [dispatch, allCachedEvents.length]);
|
}, [dispatch, allCachedEvents.length]);
|
||||||
|
|
||||||
// 默认选中第一个事件
|
// 默认选中第一个事件 - 只在当前选中的事件不在当前页时重置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPageEvents.length > 0 && !selectedEvent) {
|
if (currentPageEvents.length > 0) {
|
||||||
setSelectedEvent(currentPageEvents[0]);
|
// 检查当前选中的事件是否在当前页中
|
||||||
|
const selectedEventInCurrentPage = currentPageEvents.find(
|
||||||
|
e => e.id === selectedEvent?.id
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果选中的事件不在当前页,则选中当前页第一个事件
|
||||||
|
if (!selectedEventInCurrentPage) {
|
||||||
|
setSelectedEvent(currentPageEvents[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [currentPageEvents, selectedEvent]);
|
}, [currentPageEvents, selectedEvent?.id]);
|
||||||
|
|
||||||
|
// 组件卸载时清理选中状态
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setSelectedEvent(null);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}>
|
<Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// src/views/Community/components/DynamicNewsCard/hooks/usePagination.js
|
// src/views/Community/components/DynamicNewsCard/hooks/usePagination.js
|
||||||
// 分页逻辑自定义 Hook
|
// 分页逻辑自定义 Hook
|
||||||
|
|
||||||
import { useState, useMemo, useCallback } from 'react';
|
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
||||||
import { fetchDynamicNews } from '../../../../../store/slices/communityDataSlice';
|
import { fetchDynamicNews } from '../../../../../store/slices/communityDataSlice';
|
||||||
import { logger } from '../../../../../utils/logger';
|
import { logger } from '../../../../../utils/logger';
|
||||||
import {
|
import {
|
||||||
@@ -22,11 +22,21 @@ import {
|
|||||||
* @returns {Object} 分页状态和方法
|
* @returns {Object} 分页状态和方法
|
||||||
*/
|
*/
|
||||||
export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, toast }) => {
|
export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, toast }) => {
|
||||||
|
// 组件挂载状态跟踪 - 用于防止内存泄漏
|
||||||
|
const isMountedRef = useRef(true);
|
||||||
|
|
||||||
// 本地状态
|
// 本地状态
|
||||||
const [currentPage, setCurrentPage] = useState(PAGINATION_CONFIG.INITIAL_PAGE);
|
const [currentPage, setCurrentPage] = useState(PAGINATION_CONFIG.INITIAL_PAGE);
|
||||||
const [loadingPage, setLoadingPage] = useState(null);
|
const [loadingPage, setLoadingPage] = useState(null);
|
||||||
const [mode, setMode] = useState(DEFAULT_MODE);
|
const [mode, setMode] = useState(DEFAULT_MODE);
|
||||||
|
|
||||||
|
// 组件卸载时更新挂载状态
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
isMountedRef.current = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 根据模式决定每页显示数量
|
// 根据模式决定每页显示数量
|
||||||
const pageSize = mode === DISPLAY_MODES.CAROUSEL
|
const pageSize = mode === DISPLAY_MODES.CAROUSEL
|
||||||
? PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE
|
? PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE
|
||||||
@@ -167,6 +177,12 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
* @returns {Promise<boolean>} 是否加载成功
|
* @returns {Promise<boolean>} 是否加载成功
|
||||||
*/
|
*/
|
||||||
const loadPages = useCallback(async (missingPages, targetPage, silentMode = false) => {
|
const loadPages = useCallback(async (missingPages, targetPage, silentMode = false) => {
|
||||||
|
// 检查组件是否已卸载
|
||||||
|
if (!isMountedRef.current) {
|
||||||
|
logger.debug('DynamicNewsCard', '组件已卸载,取消加载');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!silentMode) {
|
if (!silentMode) {
|
||||||
// 显示 loading 状态
|
// 显示 loading 状态
|
||||||
setLoadingPage(targetPage);
|
setLoadingPage(targetPage);
|
||||||
@@ -182,6 +198,12 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
|
|
||||||
// 拆分为单页请求,避免 per_page 动态值导致后端返回空数据
|
// 拆分为单页请求,避免 per_page 动态值导致后端返回空数据
|
||||||
for (const page of missingPages) {
|
for (const page of missingPages) {
|
||||||
|
// 每次请求前检查组件是否已卸载
|
||||||
|
if (!isMountedRef.current) {
|
||||||
|
logger.debug('DynamicNewsCard', '组件已卸载,中止加载');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug('DynamicNewsCard', `开始加载第 ${page} 页`);
|
logger.debug('DynamicNewsCard', `开始加载第 ${page} 页`);
|
||||||
|
|
||||||
await dispatch(fetchDynamicNews({
|
await dispatch(fetchDynamicNews({
|
||||||
@@ -207,8 +229,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
missingPages
|
missingPages
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!silentMode) {
|
// 只在组件仍挂载时显示错误提示
|
||||||
// 非静默模式下显示错误提示
|
if (!silentMode && isMountedRef.current) {
|
||||||
toast({
|
toast({
|
||||||
title: '加载失败',
|
title: '加载失败',
|
||||||
description: `无法加载第 ${targetPage} 页数据,请稍后重试`,
|
description: `无法加载第 ${targetPage} 页数据,请稍后重试`,
|
||||||
@@ -221,8 +243,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
if (!silentMode) {
|
// 只在组件仍挂载时清除加载状态
|
||||||
// 清除加载状态
|
if (!silentMode && isMountedRef.current) {
|
||||||
setLoadingPage(null);
|
setLoadingPage(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +252,12 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
|
|
||||||
// 翻页处理(智能预加载)- 使用子函数重构
|
// 翻页处理(智能预加载)- 使用子函数重构
|
||||||
const handlePageChange = useCallback(async (newPage) => {
|
const handlePageChange = useCallback(async (newPage) => {
|
||||||
|
// 检查组件是否已卸载
|
||||||
|
if (!isMountedRef.current) {
|
||||||
|
logger.debug('DynamicNewsCard', '组件已卸载,取消翻页');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 🔍 诊断日志 - 记录翻页开始状态
|
// 🔍 诊断日志 - 记录翻页开始状态
|
||||||
logger.debug('DynamicNewsCard', '开始翻页', {
|
logger.debug('DynamicNewsCard', '开始翻页', {
|
||||||
currentPage,
|
currentPage,
|
||||||
@@ -260,7 +288,10 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
缺失页面: missingPages
|
缺失页面: missingPages
|
||||||
});
|
});
|
||||||
|
|
||||||
setCurrentPage(newPage);
|
// 只在组件仍挂载时更新状态
|
||||||
|
if (isMountedRef.current) {
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
}
|
||||||
await loadPages(missingPages, newPage, true); // 静默模式
|
await loadPages(missingPages, newPage, true); // 静默模式
|
||||||
} else if (missingPages.length > 0 && hasMore) {
|
} else if (missingPages.length > 0 && hasMore) {
|
||||||
// 场景B: 目标页未缓存,显示 loading 并等待加载完成
|
// 场景B: 目标页未缓存,显示 loading 并等待加载完成
|
||||||
@@ -271,7 +302,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
});
|
});
|
||||||
|
|
||||||
const success = await loadPages(missingPages, newPage, false); // 非静默模式
|
const success = await loadPages(missingPages, newPage, false); // 非静默模式
|
||||||
if (success) {
|
// 只在加载成功且组件仍挂载时更新状态
|
||||||
|
if (success && isMountedRef.current) {
|
||||||
setCurrentPage(newPage);
|
setCurrentPage(newPage);
|
||||||
}
|
}
|
||||||
} else if (missingPages.length === 0) {
|
} else if (missingPages.length === 0) {
|
||||||
@@ -282,7 +314,10 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
reason: '所有页面均已缓存'
|
reason: '所有页面均已缓存'
|
||||||
});
|
});
|
||||||
|
|
||||||
setCurrentPage(newPage);
|
// 只在组件仍挂载时更新状态
|
||||||
|
if (isMountedRef.current) {
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 场景D: 意外分支(有缺失页面但 hasMore=false)
|
// 场景D: 意外分支(有缺失页面但 hasMore=false)
|
||||||
logger.warn('DynamicNewsCard', '意外分支:有缺失页面但无法加载', {
|
logger.warn('DynamicNewsCard', '意外分支:有缺失页面但无法加载', {
|
||||||
@@ -294,16 +329,19 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
|
|||||||
cachedCount
|
cachedCount
|
||||||
});
|
});
|
||||||
|
|
||||||
setCurrentPage(newPage);
|
// 只在组件仍挂载时更新状态
|
||||||
|
if (isMountedRef.current) {
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: '数据不完整',
|
title: '数据不完整',
|
||||||
description: `第 ${newPage} 页数据可能不完整`,
|
description: `第 ${newPage} 页数据可能不完整`,
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
duration: TOAST_CONFIG.DURATION_WARNING,
|
duration: TOAST_CONFIG.DURATION_WARNING,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
currentPage,
|
currentPage,
|
||||||
|
|||||||
Reference in New Issue
Block a user