636 lines
18 KiB
TypeScript
636 lines
18 KiB
TypeScript
|
|
/**
|
|||
|
|
* 帖子差异更新 Hook
|
|||
|
|
* 接收原始帖子列表,返回优化后的帖子列表,自动处理批量更新
|
|||
|
|
* 集成 ProcessPostUseCase 进行状态管理
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|||
|
|
import {
|
|||
|
|
PostIdentifier,
|
|||
|
|
PostUpdate,
|
|||
|
|
PostUpdateType,
|
|||
|
|
PostDiffConfig,
|
|||
|
|
PostDiffResult,
|
|||
|
|
} 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,
|
|||
|
|
changeRatioThreshold = 0.5,
|
|||
|
|
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);
|
|||
|
|
|
|||
|
|
// ==================== 初始化 ====================
|
|||
|
|
|
|||
|
|
// 初始化差异计算器
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (enableDiff) {
|
|||
|
|
calculatorRef.current = createPostDiffCalculator<T>(diffConfig);
|
|||
|
|
}
|
|||
|
|
return () => {
|
|||
|
|
calculatorRef.current = null;
|
|||
|
|
};
|
|||
|
|
}, [enableDiff, diffConfig]);
|
|||
|
|
|
|||
|
|
// 初始化批量处理器
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (enableBatching) {
|
|||
|
|
batcherRef.current = createPostUpdateBatcher(batcherOptions);
|
|||
|
|
|
|||
|
|
// 订阅批量更新
|
|||
|
|
const unsubscribe = batcherRef.current.subscribe((updates) => {
|
|||
|
|
handleBatchUpdates(updates);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
unsubscribe();
|
|||
|
|
batcherRef.current?.destroy();
|
|||
|
|
batcherRef.current = null;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}, [enableBatching, batcherOptions]);
|
|||
|
|
|
|||
|
|
// ==================== 批量更新处理 ====================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理批量更新
|
|||
|
|
*/
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
// ==================== 差异计算 ====================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 计算并应用差异
|
|||
|
|
*/
|
|||
|
|
const calculateAndApplyDiff = useCallback((newPosts: T[]) => {
|
|||
|
|
const previousPosts = previousPostsRef.current;
|
|||
|
|
|
|||
|
|
// 如果帖子数量变化太大,直接重置
|
|||
|
|
const previousCount = previousPosts.length;
|
|||
|
|
const currentCount = newPosts.length;
|
|||
|
|
const changeRatio = previousCount === 0 ? 1 : Math.abs(currentCount - previousCount) / previousCount;
|
|||
|
|
|
|||
|
|
if (changeRatio > changeRatioThreshold) {
|
|||
|
|
setPosts(newPosts);
|
|||
|
|
previousPostsRef.current = newPosts;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用差异计算
|
|||
|
|
if (enableDiff && calculatorRef.current) {
|
|||
|
|
const diff = calculatorRef.current.calculateDiff(previousPosts, newPosts);
|
|||
|
|
|
|||
|
|
if (diff.shouldReset) {
|
|||
|
|
setPosts(newPosts);
|
|||
|
|
} else {
|
|||
|
|
// 将差异转换为更新操作
|
|||
|
|
const updates: PostUpdate[] = [];
|
|||
|
|
|
|||
|
|
// 处理新增
|
|||
|
|
if (diff.added.length > 0) {
|
|||
|
|
if (diff.added.length === 1) {
|
|||
|
|
updates.push({
|
|||
|
|
type: PostUpdateType.ADD,
|
|||
|
|
post: diff.added[0],
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
updates.push({
|
|||
|
|
type: PostUpdateType.BATCH_ADD,
|
|||
|
|
posts: diff.added,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理更新
|
|||
|
|
if (diff.updated.length > 0) {
|
|||
|
|
if (diff.updated.length === 1) {
|
|||
|
|
updates.push({
|
|||
|
|
type: PostUpdateType.UPDATE,
|
|||
|
|
postId: diff.updated[0].post.id,
|
|||
|
|
updates: diff.updated[0].changes,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
updates.push({
|
|||
|
|
type: PostUpdateType.BATCH_UPDATE,
|
|||
|
|
updates: diff.updated.map(u => ({
|
|||
|
|
postId: u.post.id,
|
|||
|
|
changes: u.changes,
|
|||
|
|
})),
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理删除
|
|||
|
|
if (diff.deleted.length > 0) {
|
|||
|
|
if (diff.deleted.length === 1) {
|
|||
|
|
updates.push({
|
|||
|
|
type: PostUpdateType.DELETE,
|
|||
|
|
postId: diff.deleted[0].post.id,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
updates.push({
|
|||
|
|
type: PostUpdateType.BATCH_DELETE,
|
|||
|
|
postIds: diff.deleted.map(d => d.post.id),
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理移动
|
|||
|
|
if (diff.moved.length > 0) {
|
|||
|
|
for (const move of diff.moved) {
|
|||
|
|
updates.push({
|
|||
|
|
type: PostUpdateType.MOVE,
|
|||
|
|
postId: move.post.id,
|
|||
|
|
toIndex: move.toIndex,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 应用更新
|
|||
|
|
if (updates.length > 0) {
|
|||
|
|
if (enableBatching && batcherRef.current) {
|
|||
|
|
batcherRef.current.addUpdates(updates);
|
|||
|
|
} else {
|
|||
|
|
handleBatchUpdates(updates);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 不使用差异计算,直接设置
|
|||
|
|
setPosts(newPosts);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新帖子数量限制检查
|
|||
|
|
if (newPosts.length > maxPostCount) {
|
|||
|
|
console.warn(`[useDifferentialPosts] Post count (${newPosts.length}) exceeds limit (${maxPostCount})`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
previousPostsRef.current = newPosts;
|
|||
|
|
}, [enableDiff, enableBatching, maxPostCount, changeRatioThreshold, handleBatchUpdates]);
|
|||
|
|
|
|||
|
|
// ==================== UseCase 订阅 ====================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理 UseCase 事件
|
|||
|
|
*/
|
|||
|
|
const handleUseCaseEvent = useCallback((event: PostUseCaseEvent) => {
|
|||
|
|
switch (event.type) {
|
|||
|
|
case 'state_changed': {
|
|||
|
|
const { state } = event.payload as { key: string; state: PostsState };
|
|||
|
|
if (state) {
|
|||
|
|
setLoading(state.isLoading);
|
|||
|
|
setRefreshing(state.isRefreshing);
|
|||
|
|
setError(state.error);
|
|||
|
|
setHasMore(state.hasMore);
|
|||
|
|
|
|||
|
|
// 如果帖子列表有变化,进行差异计算
|
|||
|
|
if (state.posts && state.posts.length > 0) {
|
|||
|
|
calculateAndApplyDiff(state.posts as unknown as T[]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
case 'posts_loaded': {
|
|||
|
|
const { posts: loadedPosts } = event.payload;
|
|||
|
|
if (loadedPosts) {
|
|||
|
|
calculateAndApplyDiff(loadedPosts as T[]);
|
|||
|
|
}
|
|||
|
|
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 { error: errorMsg } = event.payload;
|
|||
|
|
setError(errorMsg);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, [calculateAndApplyDiff]);
|
|||
|
|
|
|||
|
|
// 订阅 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;
|