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 // 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) {
// 检查当前选中的事件是否在当前页中
const selectedEventInCurrentPage = currentPageEvents.find(
e => e.id === selectedEvent?.id
);
// 如果选中的事件不在当前页,则选中当前页第一个事件
if (!selectedEventInCurrentPage) {
setSelectedEvent(currentPageEvents[0]); 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}>

View File

@@ -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
}); });
// 只在组件仍挂载时更新状态
if (isMountedRef.current) {
setCurrentPage(newPage); 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: '所有页面均已缓存'
}); });
// 只在组件仍挂载时更新状态
if (isMountedRef.current) {
setCurrentPage(newPage); setCurrentPage(newPage);
}
} else { } else {
// 场景D: 意外分支(有缺失页面但 hasMore=false // 场景D: 意外分支(有缺失页面但 hasMore=false
logger.warn('DynamicNewsCard', '意外分支:有缺失页面但无法加载', { logger.warn('DynamicNewsCard', '意外分支:有缺失页面但无法加载', {
@@ -294,6 +329,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
cachedCount cachedCount
}); });
// 只在组件仍挂载时更新状态
if (isMountedRef.current) {
setCurrentPage(newPage); setCurrentPage(newPage);
toast({ toast({
@@ -305,6 +342,7 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t
position: 'top' position: 'top'
}); });
} }
}
}, [ }, [
currentPage, currentPage,
pageSize, pageSize,