refactor(post): consolidate post sync to PostSyncService and remove ProcessPostUseCase
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 3m28s
Frontend CI / ota-android (push) Successful in 10m25s
Frontend CI / build-android-apk (push) Successful in 55m22s

- 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:
lafay
2026-04-13 00:26:05 +08:00
parent 6610d2f173
commit 2adc9360a5
21 changed files with 679 additions and 2896 deletions

View File

@@ -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,