refactor(post): consolidate post sync to PostSyncService and remove ProcessPostUseCase
- Replace ProcessPostUseCase with new PostSyncService in src/services/post/ - Add postListStore for state management in src/stores/post/ - Remove deprecated ProcessPostUseCase and ProcessMessageUseCase files - Delete architecture documentation files (ARCHITECTURE_REFACTOR_PLAN.md, architecture-comparison-report.md) - Update all consumers (HomeScreen, PostDetailScreen, SearchScreen, useUserProfile, useDifferentialPosts) - Simplify postService to only contain API layer methods - Remove unused type exports from message stores BREAKING CHANGE: ProcessPostUseCase and ProcessMessageUseCase have been removed. Use postSyncService for post operations instead.
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* 帖子差异更新 Hook
|
||||
* 接收原始帖子列表,返回优化后的帖子列表,自动处理批量更新
|
||||
* 集成 ProcessPostUseCase 进行状态管理
|
||||
*/
|
||||
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
PostIdentifier,
|
||||
@@ -20,98 +14,47 @@ import {
|
||||
PostBatcherOptions,
|
||||
createPostUpdateBatcher,
|
||||
} from '../infrastructure/diff/PostUpdateBatcher';
|
||||
import { processPostUseCase } from '../core/usecases/ProcessPostUseCase';
|
||||
import type { PostUseCaseEvent, PostsState } from '../core/usecases/ProcessPostUseCase';
|
||||
import { postSyncService } from '../services/post/PostSyncService';
|
||||
import type { PostsState } from '../services/post/PostSyncService';
|
||||
import { usePostListStore } from '../stores/post/postListStore';
|
||||
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;
|
||||
/** 是否首屏加载 */
|
||||
isInitialLoading: boolean;
|
||||
/** 是否正在加载更多 */
|
||||
isLoadingMore: 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;
|
||||
};
|
||||
/** 差异更新信息 */
|
||||
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> = {}
|
||||
@@ -126,8 +69,6 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
autoSubscribe = true,
|
||||
} = options;
|
||||
|
||||
// ==================== 状态 ====================
|
||||
|
||||
const [posts, setPosts] = useState<T[]>(initialPosts);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
@@ -142,23 +83,15 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
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);
|
||||
|
||||
// ==================== 批量更新处理(须在订阅 batcher 的 useEffect 之前定义)====================
|
||||
|
||||
/**
|
||||
* 处理批量更新
|
||||
*/
|
||||
const handleBatchUpdates = useCallback((updates: PostUpdate[]) => {
|
||||
setIsProcessing(true);
|
||||
|
||||
try {
|
||||
setPosts((currentPosts) => {
|
||||
setPosts(currentPosts => {
|
||||
let newPosts = [...currentPosts];
|
||||
let addedCount = 0;
|
||||
let updatedCount = 0;
|
||||
@@ -210,9 +143,7 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
const deleteUpdate = update as any;
|
||||
const prevLength = newPosts.length;
|
||||
newPosts = newPosts.filter(p => p.id !== deleteUpdate.postId);
|
||||
if (newPosts.length < prevLength) {
|
||||
deletedCount++;
|
||||
}
|
||||
if (newPosts.length < prevLength) deletedCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -243,7 +174,6 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
}
|
||||
}
|
||||
|
||||
// 更新差异统计
|
||||
if (addedCount > 0 || updatedCount > 0 || deletedCount > 0) {
|
||||
setDiffUpdates(prev => ({
|
||||
addedCount: prev.addedCount + addedCount,
|
||||
@@ -261,28 +191,17 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
|
||||
// 初始化差异计算器
|
||||
useEffect(() => {
|
||||
if (enableDiff) {
|
||||
calculatorRef.current = createPostDiffCalculator<T>(diffConfig);
|
||||
}
|
||||
return () => {
|
||||
calculatorRef.current = null;
|
||||
};
|
||||
return () => { calculatorRef.current = null; };
|
||||
}, [enableDiff, diffConfig]);
|
||||
|
||||
// 初始化批量处理器
|
||||
useEffect(() => {
|
||||
if (!enableBatching) return;
|
||||
|
||||
batcherRef.current = createPostUpdateBatcher(batcherOptions);
|
||||
|
||||
const unsubscribe = batcherRef.current.subscribe((updates) => {
|
||||
handleBatchUpdates(updates);
|
||||
});
|
||||
|
||||
const unsubscribe = batcherRef.current.subscribe(handleBatchUpdates);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
batcherRef.current?.destroy();
|
||||
@@ -290,144 +209,46 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
};
|
||||
}, [enableBatching, batcherOptions, handleBatchUpdates]);
|
||||
|
||||
// ==================== UseCase 订阅 ====================
|
||||
const syncFromStore = useCallback((state: PostsState) => {
|
||||
setLoading(state.isLoading);
|
||||
setRefreshing(state.isRefreshing);
|
||||
setError(state.error);
|
||||
setHasMore(state.hasMore);
|
||||
|
||||
/**
|
||||
* 处理 UseCase 事件
|
||||
*/
|
||||
const handleUseCaseEvent = useCallback((event: PostUseCaseEvent) => {
|
||||
switch (event.type) {
|
||||
case 'state_changed': {
|
||||
const { key: eventKey, state } = event.payload as { key: string; state: PostsState };
|
||||
// 全局订阅:只应用当前列表 key,避免其他 Tab/列表的 state 覆盖本列表(如 createPost 会更新所有 key)
|
||||
if (eventKey !== listKey) {
|
||||
break;
|
||||
}
|
||||
if (state) {
|
||||
setLoading(state.isLoading);
|
||||
setRefreshing(state.isRefreshing);
|
||||
setError(state.error);
|
||||
setHasMore(state.hasMore);
|
||||
|
||||
// ProcessPostUseCase 的 state.posts 已是合并后的完整列表。
|
||||
// 不再走差异计算 + PostUpdateBatcher:批量应用晚于 previousPostsRef 更新时,
|
||||
// BATCH_ADD 会在过短的 currentPosts 上 splice,导致连续游标加载「请求成功但列表不增长」。
|
||||
batcherRef.current?.clearPending();
|
||||
calculatorRef.current?.reset();
|
||||
if (state.posts && state.posts.length > 0) {
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'posts_loaded': {
|
||||
// fetchPosts 在 updatePostsState 时已发出 state_changed(含合并后 posts),此处不再应用,避免与其它 key 竞态或重复计算
|
||||
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': {
|
||||
const payload = event.payload as { key?: string; error?: string };
|
||||
if (payload.key !== undefined && payload.key !== listKey) {
|
||||
break;
|
||||
}
|
||||
if (payload.error !== undefined) {
|
||||
setError(payload.error);
|
||||
}
|
||||
break;
|
||||
batcherRef.current?.clearPending();
|
||||
calculatorRef.current?.reset();
|
||||
if (state.posts && state.posts.length > 0) {
|
||||
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 = [];
|
||||
}
|
||||
}, [listKey, maxPostCount]);
|
||||
}, [maxPostCount]);
|
||||
|
||||
// 订阅 UseCase 状态变化
|
||||
useEffect(() => {
|
||||
if (autoSubscribe) {
|
||||
unsubscribeRef.current = processPostUseCase.subscribe(handleUseCaseEvent);
|
||||
if (!autoSubscribe) return;
|
||||
|
||||
// 初始化时获取当前状态
|
||||
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);
|
||||
const currentState = usePostListStore.getState().getPostsState(listKey);
|
||||
syncFromStore(currentState);
|
||||
|
||||
return () => {
|
||||
if (unsubscribeRef.current) {
|
||||
unsubscribeRef.current();
|
||||
unsubscribeRef.current = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [autoSubscribe, listKey, handleUseCaseEvent]);
|
||||
const unsubscribe = usePostListStore.subscribe(state => {
|
||||
const postsState = state.postsStateMap.get(listKey);
|
||||
if (postsState) syncFromStore(postsState);
|
||||
});
|
||||
|
||||
// ==================== 操作方法 ====================
|
||||
return unsubscribe;
|
||||
}, [autoSubscribe, listKey, syncFromStore]);
|
||||
|
||||
/**
|
||||
* 刷新帖子列表
|
||||
*/
|
||||
const refresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
setError(null);
|
||||
try {
|
||||
await processPostUseCase.refreshPosts(listKey);
|
||||
await postSyncService.refreshPosts(listKey);
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : '刷新失败';
|
||||
setError(errorMsg);
|
||||
@@ -436,15 +257,12 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
}
|
||||
}, [listKey]);
|
||||
|
||||
/**
|
||||
* 加载更多帖子
|
||||
*/
|
||||
const loadMore = useCallback(async () => {
|
||||
if (hasMore && !loading) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
await processPostUseCase.loadMorePosts(listKey);
|
||||
await postSyncService.loadMorePosts(listKey);
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : '加载更多失败';
|
||||
setError(errorMsg);
|
||||
@@ -454,16 +272,8 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
}
|
||||
}, [listKey, hasMore, loading]);
|
||||
|
||||
/**
|
||||
* 手动刷新批量处理队列
|
||||
*/
|
||||
const flush = useCallback(() => {
|
||||
batcherRef.current?.flush();
|
||||
}, []);
|
||||
const flush = useCallback(() => { batcherRef.current?.flush(); }, []);
|
||||
|
||||
/**
|
||||
* 重置方法
|
||||
*/
|
||||
const reset = useCallback(() => {
|
||||
calculatorRef.current?.reset();
|
||||
batcherRef.current?.clearPending();
|
||||
@@ -473,25 +283,14 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
setError(null);
|
||||
setHasMore(true);
|
||||
previousPostsRef.current = [];
|
||||
setDiffUpdates({
|
||||
addedCount: 0,
|
||||
updatedCount: 0,
|
||||
deletedCount: 0,
|
||||
lastUpdateTime: 0,
|
||||
});
|
||||
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 {
|
||||
@@ -501,20 +300,14 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ==================== 定期更新待处理数量 ====================
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableBatching || !batcherRef.current) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setPendingUpdateCount(batcherRef.current?.getPendingCount() ?? 0);
|
||||
}, 50);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [enableBatching]);
|
||||
|
||||
// ==================== 返回值 ====================
|
||||
|
||||
return {
|
||||
posts,
|
||||
loading,
|
||||
|
||||
Reference in New Issue
Block a user