pref: 优化 useEffect 依赖和清理逻辑

This commit is contained in:
zdl
2025-11-04 16:01:56 +08:00
parent 64a441b717
commit ffa6c2f761
2 changed files with 81 additions and 24 deletions

View File

@@ -1,7 +1,7 @@
// 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 {
Card,
@@ -72,6 +72,9 @@ const DynamicNewsCard = forwardRef(({
// 本地状态
const [selectedEvent, setSelectedEvent] = useState(null);
// 初始化标记 - 确保初始加载只执行一次
const hasInitialized = useRef(false);
// 使用分页 Hook
const {
currentPage,
@@ -91,9 +94,10 @@ const DynamicNewsCard = forwardRef(({
toast
});
// 初始加载
// 初始加载 - 只在组件首次挂载且未初始化时执行
useEffect(() => {
if (allCachedEvents.length === 0) {
if (!hasInitialized.current && allCachedEvents.length === 0) {
hasInitialized.current = true;
dispatch(fetchDynamicNews({
page: PAGINATION_CONFIG.INITIAL_PAGE,
per_page: PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE,
@@ -103,12 +107,27 @@ const DynamicNewsCard = forwardRef(({
}
}, [dispatch, allCachedEvents.length]);
// 默认选中第一个事件
// 默认选中第一个事件 - 只在当前选中的事件不在当前页时重置
useEffect(() => {
if (currentPageEvents.length > 0 && !selectedEvent) {
if (currentPageEvents.length > 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 (
<Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}>

View File

@@ -1,7 +1,7 @@
// src/views/Community/components/DynamicNewsCard/hooks/usePagination.js
// 分页逻辑自定义 Hook
import { useState, useMemo, useCallback } from 'react';
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
import { fetchDynamicNews } from '../../../../../store/slices/communityDataSlice';
import { logger } from '../../../../../utils/logger';
import {
@@ -22,11 +22,21 @@ import {
* @returns {Object} 分页状态和方法
*/
export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, toast }) => {
// 组件挂载状态跟踪 - 用于防止内存泄漏
const isMountedRef = useRef(true);
// 本地状态
const [currentPage, setCurrentPage] = useState(PAGINATION_CONFIG.INITIAL_PAGE);
const [loadingPage, setLoadingPage] = useState(null);
const [mode, setMode] = useState(DEFAULT_MODE);
// 组件卸载时更新挂载状态
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);
// 根据模式决定每页显示数量
const pageSize = mode === DISPLAY_MODES.CAROUSEL
? PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE
@@ -167,6 +177,12 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
* @returns {Promise<boolean>} 是否加载成功
*/
const loadPages = useCallback(async (missingPages, targetPage, silentMode = false) => {
// 检查组件是否已卸载
if (!isMountedRef.current) {
logger.debug('DynamicNewsCard', '组件已卸载,取消加载');
return false;
}
if (!silentMode) {
// 显示 loading 状态
setLoadingPage(targetPage);
@@ -182,6 +198,12 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
// 拆分为单页请求,避免 per_page 动态值导致后端返回空数据
for (const page of missingPages) {
// 每次请求前检查组件是否已卸载
if (!isMountedRef.current) {
logger.debug('DynamicNewsCard', '组件已卸载,中止加载');
return false;
}
logger.debug('DynamicNewsCard', `开始加载第 ${page}`);
await dispatch(fetchDynamicNews({
@@ -207,8 +229,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
missingPages
});
if (!silentMode) {
// 非静默模式下显示错误提示
// 只在组件仍挂载时显示错误提示
if (!silentMode && isMountedRef.current) {
toast({
title: '加载失败',
description: `无法加载第 ${targetPage} 页数据,请稍后重试`,
@@ -221,8 +243,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
return false;
} finally {
if (!silentMode) {
// 清除加载状态
// 只在组件仍挂载时清除加载状态
if (!silentMode && isMountedRef.current) {
setLoadingPage(null);
}
}
@@ -230,6 +252,12 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
// 翻页处理(智能预加载)- 使用子函数重构
const handlePageChange = useCallback(async (newPage) => {
// 检查组件是否已卸载
if (!isMountedRef.current) {
logger.debug('DynamicNewsCard', '组件已卸载,取消翻页');
return;
}
// 🔍 诊断日志 - 记录翻页开始状态
logger.debug('DynamicNewsCard', '开始翻页', {
currentPage,
@@ -260,7 +288,10 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
缺失页面: missingPages
});
// 只在组件仍挂载时更新状态
if (isMountedRef.current) {
setCurrentPage(newPage);
}
await loadPages(missingPages, newPage, true); // 静默模式
} else if (missingPages.length > 0 && hasMore) {
// 场景B: 目标页未缓存,显示 loading 并等待加载完成
@@ -271,7 +302,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
});
const success = await loadPages(missingPages, newPage, false); // 非静默模式
if (success) {
// 只在加载成功且组件仍挂载时更新状态
if (success && isMountedRef.current) {
setCurrentPage(newPage);
}
} else if (missingPages.length === 0) {
@@ -282,7 +314,10 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
reason: '所有页面均已缓存'
});
// 只在组件仍挂载时更新状态
if (isMountedRef.current) {
setCurrentPage(newPage);
}
} else {
// 场景D: 意外分支(有缺失页面但 hasMore=false
logger.warn('DynamicNewsCard', '意外分支:有缺失页面但无法加载', {
@@ -294,6 +329,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
cachedCount
});
// 只在组件仍挂载时更新状态
if (isMountedRef.current) {
setCurrentPage(newPage);
toast({
@@ -305,6 +342,7 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
position: 'top'
});
}
}
}, [
currentPage,
pageSize,