refactor: standardize pagination response structure and enhance cursor handling
- Update various services and hooks to replace 'items' with 'list' for consistency in pagination responses. - Modify PostRepository, commentService, groupService, messageService, and postService to align with the new response structure. - Enhance cursor pagination logic in hooks and components to ensure proper handling of pagination states. - Refactor HomeScreen, PostDetailScreen, SearchScreen, and other components to utilize the updated pagination structure.
This commit is contained in:
@@ -4,13 +4,12 @@
|
||||
* 集成 ProcessPostUseCase 进行状态管理
|
||||
*/
|
||||
|
||||
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
PostIdentifier,
|
||||
PostUpdate,
|
||||
PostUpdateType,
|
||||
PostDiffConfig,
|
||||
PostDiffResult,
|
||||
} from '../infrastructure/diff/postTypes';
|
||||
import {
|
||||
PostDiffCalculator,
|
||||
@@ -119,7 +118,6 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
enableDiff = true,
|
||||
enableBatching = true,
|
||||
maxPostCount = 10000,
|
||||
changeRatioThreshold = 0.5,
|
||||
listKey = 'default',
|
||||
autoSubscribe = true,
|
||||
} = options;
|
||||
@@ -147,37 +145,7 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
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]);
|
||||
|
||||
// ==================== 批量更新处理 ====================
|
||||
// ==================== 批量更新处理(须在订阅 batcher 的 useEffect 之前定义)====================
|
||||
|
||||
/**
|
||||
* 处理批量更新
|
||||
@@ -289,123 +257,34 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ==================== 差异计算 ====================
|
||||
// ==================== 初始化 ====================
|
||||
|
||||
/**
|
||||
* 计算并应用差异
|
||||
*/
|
||||
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;
|
||||
// 初始化差异计算器
|
||||
useEffect(() => {
|
||||
if (enableDiff) {
|
||||
calculatorRef.current = createPostDiffCalculator<T>(diffConfig);
|
||||
}
|
||||
return () => {
|
||||
calculatorRef.current = null;
|
||||
};
|
||||
}, [enableDiff, diffConfig]);
|
||||
|
||||
// 使用差异计算
|
||||
if (enableDiff && calculatorRef.current) {
|
||||
const diff = calculatorRef.current.calculateDiff(previousPosts, newPosts);
|
||||
// 初始化批量处理器
|
||||
useEffect(() => {
|
||||
if (!enableBatching) return;
|
||||
|
||||
if (diff.shouldReset) {
|
||||
setPosts(newPosts);
|
||||
} else {
|
||||
// 将差异转换为更新操作
|
||||
const updates: PostUpdate[] = [];
|
||||
batcherRef.current = createPostUpdateBatcher(batcherOptions);
|
||||
|
||||
// 处理新增
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
const unsubscribe = batcherRef.current.subscribe((updates) => {
|
||||
handleBatchUpdates(updates);
|
||||
});
|
||||
|
||||
// 处理更新
|
||||
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]);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
batcherRef.current?.destroy();
|
||||
batcherRef.current = null;
|
||||
};
|
||||
}, [enableBatching, batcherOptions, handleBatchUpdates]);
|
||||
|
||||
// ==================== UseCase 订阅 ====================
|
||||
|
||||
@@ -415,26 +294,39 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
const handleUseCaseEvent = useCallback((event: PostUseCaseEvent) => {
|
||||
switch (event.type) {
|
||||
case 'state_changed': {
|
||||
const { state } = event.payload as { key: string; state: PostsState };
|
||||
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) {
|
||||
calculateAndApplyDiff(state.posts as unknown as T[]);
|
||||
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': {
|
||||
const { posts: loadedPosts } = event.payload;
|
||||
if (loadedPosts) {
|
||||
calculateAndApplyDiff(loadedPosts as T[]);
|
||||
}
|
||||
// fetchPosts 在 updatePostsState 时已发出 state_changed(含合并后 posts),此处不再应用,避免与其它 key 竞态或重复计算
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -485,12 +377,17 @@ export function useDifferentialPosts<T extends PostIdentifier = Post>(
|
||||
}
|
||||
|
||||
case 'error_occurred': {
|
||||
const { error: errorMsg } = event.payload;
|
||||
setError(errorMsg);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}, [calculateAndApplyDiff]);
|
||||
}, [listKey, maxPostCount]);
|
||||
|
||||
// 订阅 UseCase 状态变化
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user