refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 帖子差异更新 Hook
|
|
|
|
|
|
* 接收原始帖子列表,返回优化后的帖子列表,自动处理批量更新
|
|
|
|
|
|
* 集成 ProcessPostUseCase 进行状态管理
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
import {
|
|
|
|
|
|
PostIdentifier,
|
|
|
|
|
|
PostUpdate,
|
|
|
|
|
|
PostUpdateType,
|
|
|
|
|
|
PostDiffConfig,
|
|
|
|
|
|
} from '../infrastructure/diff/postTypes';
|
|
|
|
|
|
import {
|
|
|
|
|
|
PostDiffCalculator,
|
|
|
|
|
|
createPostDiffCalculator,
|
|
|
|
|
|
} from '../infrastructure/diff/PostDiffCalculator';
|
|
|
|
|
|
import {
|
|
|
|
|
|
PostUpdateBatcher,
|
|
|
|
|
|
PostBatcherOptions,
|
|
|
|
|
|
createPostUpdateBatcher,
|
|
|
|
|
|
} from '../infrastructure/diff/PostUpdateBatcher';
|
|
|
|
|
|
import { processPostUseCase } from '../core/usecases/ProcessPostUseCase';
|
|
|
|
|
|
import type { PostUseCaseEvent, PostsState } from '../core/usecases/ProcessPostUseCase';
|
|
|
|
|
|
import type { Post } from '../core/entities/Post';
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 类型定义 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 差异更新 Hook 配置选项
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface UseDifferentialPostsOptions<T extends PostIdentifier> {
|
|
|
|
|
|
/** 差异计算配置 */
|
|
|
|
|
|
diffConfig?: PostDiffConfig;
|
|
|
|
|
|
/** 批量处理配置 */
|
|
|
|
|
|
batcherOptions?: PostBatcherOptions;
|
|
|
|
|
|
/** 是否启用差异计算,默认 true */
|
|
|
|
|
|
enableDiff?: boolean;
|
|
|
|
|
|
/** 是否启用批量处理,默认 true */
|
|
|
|
|
|
enableBatching?: boolean;
|
|
|
|
|
|
/** 最大帖子数量限制,超出时触发重置 */
|
|
|
|
|
|
maxPostCount?: number;
|
|
|
|
|
|
/** 变化比例阈值(0-1),超过此值触发重置 */
|
|
|
|
|
|
changeRatioThreshold?: number;
|
|
|
|
|
|
/** 列表键(用于区分不同的帖子列表) */
|
|
|
|
|
|
listKey?: string;
|
|
|
|
|
|
/** 是否自动订阅 UseCase 状态变化,默认 true */
|
|
|
|
|
|
autoSubscribe?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 差异更新信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface DiffUpdatesInfo {
|
|
|
|
|
|
/** 新增数量 */
|
|
|
|
|
|
addedCount: number;
|
|
|
|
|
|
/** 更新数量 */
|
|
|
|
|
|
updatedCount: number;
|
|
|
|
|
|
/** 删除数量 */
|
|
|
|
|
|
deletedCount: number;
|
|
|
|
|
|
/** 上次更新时间 */
|
|
|
|
|
|
lastUpdateTime: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 差异更新 Hook 返回值
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface UseDifferentialPostsResult<T extends PostIdentifier> {
|
|
|
|
|
|
/** 优化后的帖子列表 */
|
|
|
|
|
|
posts: T[];
|
|
|
|
|
|
/** 是否正在加载 */
|
|
|
|
|
|
loading: boolean;
|
|
|
|
|
|
/** 是否正在刷新 */
|
|
|
|
|
|
refreshing: boolean;
|
|
|
|
|
|
/** 错误信息 */
|
|
|
|
|
|
error: string | null;
|
|
|
|
|
|
/** 是否有更多数据 */
|
|
|
|
|
|
hasMore: boolean;
|
|
|
|
|
|
/** 待处理更新数量 */
|
|
|
|
|
|
pendingUpdateCount: number;
|
|
|
|
|
|
/** 是否正在处理更新 */
|
|
|
|
|
|
isProcessing: boolean;
|
|
|
|
|
|
/** 刷新方法 */
|
|
|
|
|
|
refresh: () => Promise<void>;
|
|
|
|
|
|
/** 加载更多方法 */
|
|
|
|
|
|
loadMore: () => Promise<void>;
|
|
|
|
|
|
/** 手动刷新批量处理队列 */
|
|
|
|
|
|
flush: () => void;
|
|
|
|
|
|
/** 重置方法 */
|
|
|
|
|
|
reset: () => void;
|
|
|
|
|
|
/** 强制更新(跳过批量处理) */
|
|
|
|
|
|
forceUpdate: (posts: T[]) => void;
|
|
|
|
|
|
/** 获取差异统计信息 */
|
|
|
|
|
|
getDiffStats: () => {
|
|
|
|
|
|
totalBatches: number;
|
|
|
|
|
|
totalUpdates: number;
|
|
|
|
|
|
averageBatchSize: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
/** 差异更新信息 */
|
|
|
|
|
|
diffUpdates: DiffUpdatesInfo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Hook 实现 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 帖子差异更新 Hook
|
|
|
|
|
|
* @param initialPosts 初始帖子列表(可选)
|
|
|
|
|
|
* @param options 配置选项
|
|
|
|
|
|
* @returns 优化后的帖子列表和相关方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
|
|
|
|
|
initialPosts: T[] = [],
|
|
|
|
|
|
options: UseDifferentialPostsOptions<T> = {}
|
|
|
|
|
|
): UseDifferentialPostsResult<T> {
|
|
|
|
|
|
const {
|
|
|
|
|
|
diffConfig,
|
|
|
|
|
|
batcherOptions,
|
|
|
|
|
|
enableDiff = true,
|
|
|
|
|
|
enableBatching = true,
|
|
|
|
|
|
maxPostCount = 10000,
|
|
|
|
|
|
listKey = 'default',
|
|
|
|
|
|
autoSubscribe = true,
|
|
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 状态 ====================
|
|
|
|
|
|
|
|
|
|
|
|
const [posts, setPosts] = useState<T[]>(initialPosts);
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
|
const [hasMore, setHasMore] = useState(true);
|
|
|
|
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
|
|
const [pendingUpdateCount, setPendingUpdateCount] = useState(0);
|
|
|
|
|
|
const [diffUpdates, setDiffUpdates] = useState<DiffUpdatesInfo>({
|
|
|
|
|
|
addedCount: 0,
|
|
|
|
|
|
updatedCount: 0,
|
|
|
|
|
|
deletedCount: 0,
|
|
|
|
|
|
lastUpdateTime: 0,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Refs ====================
|
|
|
|
|
|
|
|
|
|
|
|
const calculatorRef = useRef<PostDiffCalculator<T> | null>(null);
|
|
|
|
|
|
const batcherRef = useRef<PostUpdateBatcher | null>(null);
|
|
|
|
|
|
const previousPostsRef = useRef<T[]>(initialPosts);
|
|
|
|
|
|
const unsubscribeRef = useRef<(() => void) | null>(null);
|
|
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
// ==================== 批量更新处理(须在订阅 batcher 的 useEffect 之前定义)====================
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理批量更新
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleBatchUpdates = useCallback((updates: PostUpdate[]) => {
|
|
|
|
|
|
setIsProcessing(true);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
setPosts((currentPosts) => {
|
|
|
|
|
|
let newPosts = [...currentPosts];
|
|
|
|
|
|
let addedCount = 0;
|
|
|
|
|
|
let updatedCount = 0;
|
|
|
|
|
|
let deletedCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (const update of updates) {
|
|
|
|
|
|
switch (update.type) {
|
|
|
|
|
|
case PostUpdateType.ADD: {
|
|
|
|
|
|
const addUpdate = update as any;
|
|
|
|
|
|
const index = addUpdate.index ?? newPosts.length;
|
|
|
|
|
|
newPosts.splice(index, 0, addUpdate.post as T);
|
|
|
|
|
|
addedCount++;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case PostUpdateType.BATCH_ADD: {
|
|
|
|
|
|
const batchAddUpdate = update as any;
|
|
|
|
|
|
const postsToAdd = batchAddUpdate.posts || [];
|
|
|
|
|
|
const startIndex = batchAddUpdate.startIndex ?? newPosts.length;
|
|
|
|
|
|
newPosts.splice(startIndex, 0, ...postsToAdd as T[]);
|
|
|
|
|
|
addedCount += postsToAdd.length;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case PostUpdateType.UPDATE: {
|
|
|
|
|
|
const updatePost = update as any;
|
|
|
|
|
|
const index = newPosts.findIndex(p => p.id === updatePost.postId);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
newPosts[index] = { ...newPosts[index], ...updatePost.updates };
|
|
|
|
|
|
updatedCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case PostUpdateType.BATCH_UPDATE: {
|
|
|
|
|
|
const batchUpdate = update as any;
|
|
|
|
|
|
const updatesList = batchUpdate.updates || [];
|
|
|
|
|
|
for (const { postId, changes } of updatesList) {
|
|
|
|
|
|
const index = newPosts.findIndex(p => p.id === postId);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
newPosts[index] = { ...newPosts[index], ...changes };
|
|
|
|
|
|
updatedCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case PostUpdateType.DELETE: {
|
|
|
|
|
|
const deleteUpdate = update as any;
|
|
|
|
|
|
const prevLength = newPosts.length;
|
|
|
|
|
|
newPosts = newPosts.filter(p => p.id !== deleteUpdate.postId);
|
|
|
|
|
|
if (newPosts.length < prevLength) {
|
|
|
|
|
|
deletedCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case PostUpdateType.BATCH_DELETE: {
|
|
|
|
|
|
const batchDelete = update as any;
|
|
|
|
|
|
const idsToDelete = new Set(batchDelete.postIds || []);
|
|
|
|
|
|
const prevLength = newPosts.length;
|
|
|
|
|
|
newPosts = newPosts.filter(p => !idsToDelete.has(p.id));
|
|
|
|
|
|
deletedCount += prevLength - newPosts.length;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case PostUpdateType.MOVE: {
|
|
|
|
|
|
const moveUpdate = update as any;
|
|
|
|
|
|
const fromIndex = newPosts.findIndex(p => p.id === moveUpdate.postId);
|
|
|
|
|
|
if (fromIndex !== -1) {
|
|
|
|
|
|
const [movedPost] = newPosts.splice(fromIndex, 1);
|
|
|
|
|
|
newPosts.splice(moveUpdate.toIndex, 0, movedPost);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case PostUpdateType.RESET: {
|
|
|
|
|
|
const resetUpdate = update as any;
|
|
|
|
|
|
newPosts = resetUpdate.posts || [];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新差异统计
|
|
|
|
|
|
if (addedCount > 0 || updatedCount > 0 || deletedCount > 0) {
|
|
|
|
|
|
setDiffUpdates(prev => ({
|
|
|
|
|
|
addedCount: prev.addedCount + addedCount,
|
|
|
|
|
|
updatedCount: prev.updatedCount + updatedCount,
|
|
|
|
|
|
deletedCount: prev.deletedCount + deletedCount,
|
|
|
|
|
|
lastUpdateTime: Date.now(),
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return newPosts;
|
|
|
|
|
|
});
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsProcessing(false);
|
|
|
|
|
|
setPendingUpdateCount(batcherRef.current?.getPendingCount() ?? 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
// ==================== 初始化 ====================
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
// 初始化差异计算器
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (enableDiff) {
|
|
|
|
|
|
calculatorRef.current = createPostDiffCalculator<T>(diffConfig);
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
}
|
2026-03-23 03:58:26 +08:00
|
|
|
|
return () => {
|
|
|
|
|
|
calculatorRef.current = null;
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [enableDiff, diffConfig]);
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
// 初始化批量处理器
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!enableBatching) return;
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
batcherRef.current = createPostUpdateBatcher(batcherOptions);
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
const unsubscribe = batcherRef.current.subscribe((updates) => {
|
|
|
|
|
|
handleBatchUpdates(updates);
|
|
|
|
|
|
});
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
2026-03-23 03:58:26 +08:00
|
|
|
|
return () => {
|
|
|
|
|
|
unsubscribe();
|
|
|
|
|
|
batcherRef.current?.destroy();
|
|
|
|
|
|
batcherRef.current = null;
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [enableBatching, batcherOptions, handleBatchUpdates]);
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== UseCase 订阅 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理 UseCase 事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleUseCaseEvent = useCallback((event: PostUseCaseEvent) => {
|
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
|
case 'state_changed': {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
const { key: eventKey, state } = event.payload as { key: string; state: PostsState };
|
|
|
|
|
|
// 全局订阅:只应用当前列表 key,避免其他 Tab/列表的 state 覆盖本列表(如 createPost 会更新所有 key)
|
|
|
|
|
|
if (eventKey !== listKey) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
if (state) {
|
|
|
|
|
|
setLoading(state.isLoading);
|
|
|
|
|
|
setRefreshing(state.isRefreshing);
|
|
|
|
|
|
setError(state.error);
|
|
|
|
|
|
setHasMore(state.hasMore);
|
2026-03-23 03:58:26 +08:00
|
|
|
|
|
|
|
|
|
|
// ProcessPostUseCase 的 state.posts 已是合并后的完整列表。
|
|
|
|
|
|
// 不再走差异计算 + PostUpdateBatcher:批量应用晚于 previousPostsRef 更新时,
|
|
|
|
|
|
// BATCH_ADD 会在过短的 currentPosts 上 splice,导致连续游标加载「请求成功但列表不增长」。
|
|
|
|
|
|
batcherRef.current?.clearPending();
|
|
|
|
|
|
calculatorRef.current?.reset();
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
if (state.posts && state.posts.length > 0) {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
const next = state.posts as unknown as T[];
|
|
|
|
|
|
if (next.length > maxPostCount) {
|
|
|
|
|
|
console.warn(`[useDifferentialPosts] Post count (${next.length}) exceeds limit (${maxPostCount})`);
|
|
|
|
|
|
}
|
|
|
|
|
|
setPosts(next);
|
|
|
|
|
|
previousPostsRef.current = next;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setPosts([]);
|
|
|
|
|
|
previousPostsRef.current = [];
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'posts_loaded': {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
// fetchPosts 在 updatePostsState 时已发出 state_changed(含合并后 posts),此处不再应用,避免与其它 key 竞态或重复计算
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'post_created': {
|
|
|
|
|
|
const { post } = event.payload;
|
|
|
|
|
|
if (post) {
|
|
|
|
|
|
// 新帖子添加到列表开头
|
|
|
|
|
|
setPosts(prev => [post as T, ...prev]);
|
|
|
|
|
|
setDiffUpdates(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
addedCount: prev.addedCount + 1,
|
|
|
|
|
|
lastUpdateTime: Date.now(),
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'post_updated': {
|
|
|
|
|
|
const { post } = event.payload;
|
|
|
|
|
|
if (post) {
|
|
|
|
|
|
setPosts(prev => prev.map(p => p.id === post.id ? { ...p, ...post } as T : p));
|
|
|
|
|
|
setDiffUpdates(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
updatedCount: prev.updatedCount + 1,
|
|
|
|
|
|
lastUpdateTime: Date.now(),
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'post_deleted': {
|
|
|
|
|
|
const { postId } = event.payload;
|
|
|
|
|
|
if (postId) {
|
|
|
|
|
|
setPosts(prev => prev.filter(p => p.id !== postId));
|
|
|
|
|
|
setDiffUpdates(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
deletedCount: prev.deletedCount + 1,
|
|
|
|
|
|
lastUpdateTime: Date.now(),
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'loading_changed': {
|
|
|
|
|
|
const { isLoading } = event.payload;
|
|
|
|
|
|
setLoading(isLoading);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'error_occurred': {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
const payload = event.payload as { key?: string; error?: string };
|
|
|
|
|
|
if (payload.key !== undefined && payload.key !== listKey) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (payload.error !== undefined) {
|
|
|
|
|
|
setError(payload.error);
|
|
|
|
|
|
}
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-23 03:58:26 +08:00
|
|
|
|
}, [listKey, maxPostCount]);
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 订阅 UseCase 状态变化
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (autoSubscribe) {
|
|
|
|
|
|
unsubscribeRef.current = processPostUseCase.subscribe(handleUseCaseEvent);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化时获取当前状态
|
|
|
|
|
|
const currentState = processPostUseCase.getPostsState(listKey);
|
|
|
|
|
|
if (currentState.posts.length > 0) {
|
|
|
|
|
|
setPosts(currentState.posts as unknown as T[]);
|
|
|
|
|
|
previousPostsRef.current = currentState.posts as unknown as T[];
|
|
|
|
|
|
}
|
|
|
|
|
|
setLoading(currentState.isLoading);
|
|
|
|
|
|
setRefreshing(currentState.isRefreshing);
|
|
|
|
|
|
setError(currentState.error);
|
|
|
|
|
|
setHasMore(currentState.hasMore);
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
if (unsubscribeRef.current) {
|
|
|
|
|
|
unsubscribeRef.current();
|
|
|
|
|
|
unsubscribeRef.current = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [autoSubscribe, listKey, handleUseCaseEvent]);
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 操作方法 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 刷新帖子列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
const refresh = useCallback(async () => {
|
|
|
|
|
|
setRefreshing(true);
|
|
|
|
|
|
setError(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await processPostUseCase.refreshPosts(listKey);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : '刷新失败';
|
|
|
|
|
|
setError(errorMsg);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [listKey]);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载更多帖子
|
|
|
|
|
|
*/
|
|
|
|
|
|
const loadMore = useCallback(async () => {
|
|
|
|
|
|
if (hasMore && !loading) {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
setError(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await processPostUseCase.loadMorePosts(listKey);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : '加载更多失败';
|
|
|
|
|
|
setError(errorMsg);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [listKey, hasMore, loading]);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 手动刷新批量处理队列
|
|
|
|
|
|
*/
|
|
|
|
|
|
const flush = useCallback(() => {
|
|
|
|
|
|
batcherRef.current?.flush();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重置方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
const reset = useCallback(() => {
|
|
|
|
|
|
calculatorRef.current?.reset();
|
|
|
|
|
|
batcherRef.current?.clearPending();
|
|
|
|
|
|
setPosts([]);
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
|
setError(null);
|
|
|
|
|
|
setHasMore(true);
|
|
|
|
|
|
previousPostsRef.current = [];
|
|
|
|
|
|
setDiffUpdates({
|
|
|
|
|
|
addedCount: 0,
|
|
|
|
|
|
updatedCount: 0,
|
|
|
|
|
|
deletedCount: 0,
|
|
|
|
|
|
lastUpdateTime: 0,
|
|
|
|
|
|
});
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 强制更新方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
const forceUpdate = useCallback((newPosts: T[]) => {
|
|
|
|
|
|
setPosts(newPosts);
|
|
|
|
|
|
previousPostsRef.current = newPosts;
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取统计信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getDiffStats = useCallback(() => {
|
|
|
|
|
|
const batcherStats = batcherRef.current?.getStats();
|
|
|
|
|
|
return {
|
|
|
|
|
|
totalBatches: batcherStats?.totalBatches ?? 0,
|
|
|
|
|
|
totalUpdates: batcherStats?.totalUpdates ?? 0,
|
|
|
|
|
|
averageBatchSize: batcherStats?.averageBatchSize ?? 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 定期更新待处理数量 ====================
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!enableBatching || !batcherRef.current) return;
|
|
|
|
|
|
|
|
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
|
|
setPendingUpdateCount(batcherRef.current?.getPendingCount() ?? 0);
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
|
|
|
|
|
|
return () => clearInterval(interval);
|
|
|
|
|
|
}, [enableBatching]);
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 返回值 ====================
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
posts,
|
|
|
|
|
|
loading,
|
|
|
|
|
|
refreshing,
|
|
|
|
|
|
error,
|
|
|
|
|
|
hasMore,
|
|
|
|
|
|
pendingUpdateCount,
|
|
|
|
|
|
isProcessing,
|
|
|
|
|
|
refresh,
|
|
|
|
|
|
loadMore,
|
|
|
|
|
|
flush,
|
|
|
|
|
|
reset,
|
|
|
|
|
|
forceUpdate,
|
|
|
|
|
|
getDiffStats,
|
|
|
|
|
|
diffUpdates,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default useDifferentialPosts;
|