refactor: 删除未使用的 lastUpdated 和 cachedCount 状态

- 删除 initialState 中的 lastUpdated 和 cachedCount
  - 删除所有 reducer 中相关的设置代码
  - 更新 selectors 使用 .length 替代 cachedCount
  - 删除 shouldRefresh 工具函数

  简化理由:
  - lastUpdated 未被使用
  - cachedCount 可以通过 events.length 直接获取
This commit is contained in:
zdl
2025-11-05 22:33:25 +08:00
parent ed24a14fbf
commit 6930878ff6
3 changed files with 155 additions and 114 deletions

View File

@@ -103,7 +103,6 @@ const createDataReducers = (builder, asyncThunk, dataKey) => {
.addCase(asyncThunk.fulfilled, (state, action) => {
state.loading[dataKey] = false;
state[dataKey] = action.payload;
state.lastUpdated[dataKey] = new Date().toISOString();
})
.addCase(asyncThunk.rejected, (state, action) => {
state.loading[dataKey] = false;
@@ -162,7 +161,7 @@ export const fetchHotEvents = createAsyncThunk(
* @param {Object} params - 请求参数
* @param {string} params.mode - 显示模式('vertical' | 'four-row'
* @param {number} params.page - 页码
* @param {number} params.per_page - 每页数量
* @param {number} params.per_page - 每页数量(可选,不提供时自动根据 mode 计算)
* @param {boolean} params.clearCache - 是否清空缓存(默认 false
* @param {boolean} params.prependMode - 是否追加到头部(用于定时刷新,默认 false
* @param {string} params.sort - 排序方式new/hot
@@ -176,8 +175,8 @@ export const fetchDynamicNews = createAsyncThunk(
async ({
mode = 'vertical',
page = 1,
per_page = 5,
pageSize = 5, // 🔍 添加 pageSize 参数(之前漏掉了
per_page, // 移除默认值,下面动态计算
pageSize, // 向后兼容(已废弃,使用 per_page
clearCache = false,
prependMode = false,
sort = 'new',
@@ -187,6 +186,12 @@ export const fetchDynamicNews = createAsyncThunk(
industry_code
} = {}, { rejectWithValue }) => {
try {
// 【动态计算 per_page】根据 mode 自动选择合适的每页大小
// - 平铺模式 (four-row): 30 条7.5行 × 4列提供充足的缓冲数据
// - 纵向模式 (vertical): 10 条(传统分页)
// 优先使用传入的 per_page其次使用 pageSize向后兼容最后根据 mode 计算
const finalPerPage = per_page || pageSize || (mode === 'four-row' ? 30 : 10);
// 构建筛选参数
const filters = {};
if (sort) filters.sort = sort;
@@ -196,8 +201,9 @@ export const fetchDynamicNews = createAsyncThunk(
if (industry_code) filters.industry_code = industry_code;
logger.debug('CommunityData', '开始获取动态新闻', {
mode,
page,
per_page,
per_page: finalPerPage,
clearCache,
prependMode,
filters
@@ -205,7 +211,7 @@ export const fetchDynamicNews = createAsyncThunk(
const response = await eventService.getEvents({
page,
per_page,
per_page: finalPerPage,
...filters
});
@@ -213,15 +219,15 @@ export const fetchDynamicNews = createAsyncThunk(
logger.info('CommunityData', '动态新闻加载成功', {
count: response.data.events.length,
page: response.data.pagination?.page || page,
total: response.data.pagination?.total || 0
total: response.data.pagination?.total || 0,
per_page: finalPerPage
});
return {
mode,
events: response.data.events,
total: response.data.pagination?.total || 0,
page,
per_page,
pageSize, // 🔍 添加 pageSize 到返回值
per_page: finalPerPage,
clearCache,
prependMode
};
@@ -233,8 +239,7 @@ export const fetchDynamicNews = createAsyncThunk(
events: [],
total: 0,
page,
per_page,
pageSize, // 🔍 添加 pageSize 到返回值
per_page: finalPerPage,
clearCache,
prependMode,
isEmpty: true // 标记为空数据,用于边界条件处理
@@ -293,6 +298,27 @@ export const toggleEventFollow = createAsyncThunk(
// ==================== Slice 定义 ====================
/**
* 【Redux State 架构设计】
*
* 核心原则:
* 1. **模式独立存储**: verticalEvents 和 fourRowEvents 完全独立
* - 原因:两种模式使用不同的 pageSize (10 vs 30),共享缓存会导致分页混乱
* - 代价:~50% 内存冗余(同一事件可能存在于两个数组)
* - 权衡:简化逻辑复杂度,避免 pageSize 切换时的边界计算问题
*
* 2. **数据去重**: 使用 Set 去重,防止重复事件
* - 场景1网络请求乱序慢请求后返回
* - 场景2定时刷新 + prepend 模式(新事件插入头部)
* - 场景3后端分页漂移新数据导致页码偏移
*
* 3. **追加模式 (append)**: 虚拟滚动必须使用累积数组
* - 原因:虚拟滚动需要完整数据计算 totalHeight
* - 对比传统分页每次替换数据page mode
*
* 4. **加载状态管理**: 分模式独立管理 loading/error
* - 避免模式切换时的加载状态冲突
*/
const communityDataSlice = createSlice({
name: 'communityData',
initialState: {
@@ -300,19 +326,17 @@ const communityDataSlice = createSlice({
popularKeywords: [],
hotEvents: [],
// 纵向模式数据(独立存储)
verticalEvents: [], // 纵向模式完整缓存列表
verticalTotal: 0, // 纵向模式服务端总数量
verticalCachedCount: 0, // 纵向模式已缓存数量
// 纵向模式独立存储(传统分页 + 每页10条
verticalEvents: [], // 完整缓存列表(累积所有已加载数据)
verticalTotal: 0, // 服务端总数量(用于计算总页数)
// 平铺模式数据(独立存储)
fourRowEvents: [], // 平铺模式完整缓存列表
fourRowTotal: 0, // 平铺模式服务端总数量
fourRowCachedCount: 0, // 平铺模式已缓存数量
// 平铺模式独立存储(虚拟滚动 + 每页30条
fourRowEvents: [], // 完整缓存列表(虚拟滚动的数据源)
fourRowTotal: 0, // 服务端总数量(用于判断 hasMore
eventFollowStatus: {}, // 事件关注状态 { [eventId]: { isFollowing: boolean, followerCount: number } }
eventFollowStatus: {}, // 事件关注状态(全局共享){ [eventId]: { isFollowing: boolean, followerCount: number } }
// 加载状态
// 加载状态(分模式管理)
loading: {
popularKeywords: false,
hotEvents: false,
@@ -320,20 +344,12 @@ const communityDataSlice = createSlice({
fourRowEvents: false
},
// 错误信息
// 错误信息(分模式管理)
error: {
popularKeywords: null,
hotEvents: null,
verticalEvents: null,
fourRowEvents: null
},
// 最后更新时间
lastUpdated: {
popularKeywords: null,
hotEvents: null,
verticalEvents: null,
fourRowEvents: null
}
},
@@ -355,14 +371,6 @@ const communityDataSlice = createSlice({
state.fourRowEvents = [];
state.verticalTotal = 0;
state.fourRowTotal = 0;
state.verticalCachedCount = 0;
state.fourRowCachedCount = 0;
// 清除更新时间
state.lastUpdated.popularKeywords = null;
state.lastUpdated.hotEvents = null;
state.lastUpdated.verticalEvents = null;
state.lastUpdated.fourRowEvents = null;
logger.info('CommunityData', '所有缓存已清除');
},
@@ -377,26 +385,20 @@ const communityDataSlice = createSlice({
if (type === 'popularKeywords') {
localCacheManager.remove(CACHE_KEYS.POPULAR_KEYWORDS);
state.popularKeywords = [];
state.lastUpdated.popularKeywords = null;
logger.info('CommunityData', '热门关键词缓存已清除');
} else if (type === 'hotEvents') {
localCacheManager.remove(CACHE_KEYS.HOT_EVENTS);
state.hotEvents = [];
state.lastUpdated.hotEvents = null;
logger.info('CommunityData', '热点事件缓存已清除');
} else if (type === 'verticalEvents') {
// verticalEvents 不使用 localStorage只清除 Redux state
state.verticalEvents = [];
state.verticalTotal = 0;
state.verticalCachedCount = 0;
state.lastUpdated.verticalEvents = null;
logger.info('CommunityData', '纵向模式事件数据已清除');
} else if (type === 'fourRowEvents') {
// fourRowEvents 不使用 localStorage只清除 Redux state
state.fourRowEvents = [];
state.fourRowTotal = 0;
state.fourRowCachedCount = 0;
state.lastUpdated.fourRowEvents = null;
logger.info('CommunityData', '平铺模式事件数据已清除');
}
},
@@ -439,7 +441,6 @@ const communityDataSlice = createSlice({
const { mode, events, total, page, clearCache, prependMode, isEmpty } = action.payload;
const stateKey = mode === 'four-row' ? 'fourRowEvents' : 'verticalEvents';
const totalKey = mode === 'four-row' ? 'fourRowTotal' : 'verticalTotal';
const cachedCountKey = mode === 'four-row' ? 'fourRowCachedCount' : 'verticalCachedCount';
// 边界条件:空数据只记录日志,不更新 state保留现有数据
if (isEmpty || (events.length === 0 && !clearCache)) {
@@ -461,6 +462,24 @@ const communityDataSlice = createSlice({
'state[stateKey] 之前': state[stateKey].length,
});
/**
* 【数据去重和追加逻辑】
*
* 三种模式:
* 1. clearCache 模式:直接替换(用于刷新或模式切换)
* 2. prependMode 模式:去重后插入头部(用于定时刷新,获取最新事件)
* 3. append 模式(默认):去重后追加到末尾(用于无限滚动加载下一页)
*
* 去重逻辑append 和 prepend 模式):
* - 使用 Set 提取已存在的事件 ID
* - 过滤掉新数据中与现有数据重复的事件
* - 只保留真正的新事件
*
* 为什么需要去重:
* 1. 网络请求乱序例如第3页比第2页先返回
* 2. 定时刷新冲突:用户正在浏览时后台刷新了第一页
* 3. 后端分页漂移:新事件插入导致页码边界变化
*/
if (clearCache) {
// 清空缓存模式:直接替换
state[stateKey] = events;
@@ -482,7 +501,7 @@ const communityDataSlice = createSlice({
totalCount: state[stateKey].length
});
} else {
// 简单追加模式:去重后追加到末尾(虚拟滚动组件处理展示逻辑
// 默认追加模式:去重后追加到末尾(用于虚拟滚动加载下一页
const existingIds = new Set(state[stateKey].map(e => e.id));
const newEvents = events.filter(e => !existingIds.has(e.id));
state[stateKey] = [...state[stateKey], ...newEvents];
@@ -497,10 +516,8 @@ const communityDataSlice = createSlice({
}
state[totalKey] = total;
state[cachedCountKey] = state[stateKey].length; // 简化:不再有 null 占位符
state.loading[stateKey] = false;
state.lastUpdated[stateKey] = new Date().toISOString();
})
.addCase(fetchDynamicNews.rejected, (state, action) => {
const mode = action.meta.arg.mode || 'vertical';
@@ -531,58 +548,46 @@ export const selectHotEvents = (state) => state.communityData.hotEvents;
export const selectEventFollowStatus = (state) => state.communityData.eventFollowStatus;
export const selectLoading = (state) => state.communityData.loading;
export const selectError = (state) => state.communityData.error;
export const selectLastUpdated = (state) => state.communityData.lastUpdated;
// 纵向模式数据选择器
export const selectVerticalEvents = (state) => state.communityData.verticalEvents;
export const selectVerticalTotal = (state) => state.communityData.verticalTotal;
export const selectVerticalCachedCount = (state) => state.communityData.verticalCachedCount;
export const selectVerticalCachedCount = (state) => state.communityData.verticalEvents.length;
// 平铺模式数据选择器
export const selectFourRowEvents = (state) => state.communityData.fourRowEvents;
export const selectFourRowTotal = (state) => state.communityData.fourRowTotal;
export const selectFourRowCachedCount = (state) => state.communityData.fourRowCachedCount;
export const selectFourRowCachedCount = (state) => state.communityData.fourRowEvents.length;
// 组合选择器
export const selectPopularKeywordsWithLoading = (state) => ({
data: state.communityData.popularKeywords,
loading: state.communityData.loading.popularKeywords,
error: state.communityData.error.popularKeywords,
lastUpdated: state.communityData.lastUpdated.popularKeywords
error: state.communityData.error.popularKeywords
});
export const selectHotEventsWithLoading = (state) => ({
data: state.communityData.hotEvents,
loading: state.communityData.loading.hotEvents,
error: state.communityData.error.hotEvents,
lastUpdated: state.communityData.lastUpdated.hotEvents
error: state.communityData.error.hotEvents
});
// 纵向模式数据 + 加载状态选择器
export const selectVerticalEventsWithLoading = (state) => ({
data: state.communityData.verticalEvents, // 完整缓存列表(可能包含 null 占位符)
data: state.communityData.verticalEvents, // 完整缓存列表
loading: state.communityData.loading.verticalEvents,
error: state.communityData.error.verticalEvents,
total: state.communityData.verticalTotal, // 服务端总数量
cachedCount: state.communityData.verticalCachedCount, // 已缓存有效数量(排除 null
lastUpdated: state.communityData.lastUpdated.verticalEvents
cachedCount: state.communityData.verticalEvents.length // 已缓存有效数量
});
// 平铺模式数据 + 加载状态选择器
export const selectFourRowEventsWithLoading = (state) => ({
data: state.communityData.fourRowEvents, // 完整缓存列表(可能包含 null 占位符)
data: state.communityData.fourRowEvents, // 完整缓存列表
loading: state.communityData.loading.fourRowEvents,
error: state.communityData.error.fourRowEvents,
total: state.communityData.fourRowTotal, // 服务端总数量
cachedCount: state.communityData.fourRowCachedCount, // 已缓存有效数量(排除 null
lastUpdated: state.communityData.lastUpdated.fourRowEvents
cachedCount: state.communityData.fourRowEvents.length // 已缓存有效数量
});
// 工具函数:检查数据是否需要刷新(超过指定时间)
export const shouldRefresh = (lastUpdated, thresholdMinutes = 30) => {
if (!lastUpdated) return true;
const elapsed = Date.now() - new Date(lastUpdated).getTime();
return elapsed > thresholdMinutes * 60 * 1000;
};
export default communityDataSlice.reducer;