Add useCursorPagination hook and update multiple screens and services to use cursor-based pagination for better performance and consistency. - Add useCursorPagination hook with deduplication and caching support - Add cursor pagination types to infrastructure layer - Refactor HomeScreen, PostDetailScreen, SearchScreen for posts/comments - Refactor GroupMembersScreen, JoinGroupScreen for groups/members - Refactor MessageListScreen, NotificationsScreen for messages - Update post, message, group, comment, notification services with cursor endpoints - Add CursorPaginationRequest/Response DTOs - Remove deprecated OPTIMIZATION_DESIGN.md documentation
30 KiB
30 KiB
前端游标分页适配方案设计文档
一、背景说明
后端已完成游标分页功能实现,共改造了 11 个接口。前端需要对接这些新的游标分页接口,替换现有的基于页码的分页方式。
后端游标分页接口列表
| 模块 | 接口路径 | 排序方式 |
|---|---|---|
| 帖子 | GET /posts/cursor | created_at DESC |
| 帖子搜索 | GET /posts/search/cursor | created_at DESC |
| 用户帖子 | GET /users/:id/posts/cursor | created_at DESC |
| 会话列表 | GET /conversations/cursor | updated_at DESC |
| 消息列表 | GET /conversations/:id/messages/cursor | seq DESC |
| 评论列表 | GET /comments/post/:id/cursor | created_at DESC |
| 评论回复 | GET /comments/:id/replies/cursor | created_at ASC |
| 通知列表 | GET /notifications/cursor | created_at DESC |
| 群组列表 | GET /groups/cursor | created_at DESC |
| 群成员 | GET /groups/:id/members/cursor | join_time DESC |
| 群公告 | GET /groups/:id/announcements/cursor | created_at DESC |
后端 API 规范
请求参数:
cursor- 游标字符串(首次请求不传)direction- 分页方向:forward或backward(默认 forward)page_size- 每页数量(默认 20,最大 100)
响应格式:
{
"items": [...],
"next_cursor": "xxx",
"prev_cursor": "xxx",
"has_more": true
}
二、现有前端分页机制分析
2.1 分页类型定义 (src/infrastructure/pagination/types.ts)
// 现有分页状态 - 基于页码
interface PaginationState {
currentPage: number;
pageSize: number;
totalLoaded: number;
hasMore: boolean;
isLoading: boolean;
lastLoadTime: number;
cursor?: string | number | null; // 已支持但未充分使用
hasError: boolean;
error?: string;
}
// 现有 FetchPageFunction
type FetchPageFunction<T = any> = (
page: number,
pageSize: number,
cursor?: string | number | null
) => Promise<{
data: T[];
hasMore: boolean;
cursor?: string | number | null;
}>;
2.2 分页状态管理器 (PaginationStateManager.ts)
- 使用
currentPage作为分页基准 - 基于页码的缓存机制(
pageCaches: Map<string, Map<number, PageCache>>) loadMore()方法自动递增页码
2.3 usePagination Hook (usePagination.ts)
- 暴露
currentPage、loadMore、refresh等方法 - 内部调用
PaginationStateManager进行状态管理 - 已有
cursor支持,但 UI 层仍使用页码
2.4 现有 API 服务层
postService.ts:
// 现有分页方式
async getPosts(page = 1, pageSize = 20, tab?: string): Promise<PaginatedData<Post>>
messageService.ts:
// 现有分页方式 - 基于 seq
async getMessages(conversationId, afterSeq?, beforeSeq?, limit?): Promise<MessageListResponse>
2.5 现有页面组件分页方式
各页面使用手动管理状态的方式:
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const loadMore = useCallback(() => {
if (!loading && hasMore) {
const nextPage = page + 1;
setPage(nextPage);
loadData(nextPage);
}
}, [loading, hasMore, page, loadData]);
涉及页面:
HomeScreen.tsx- 帖子列表SearchScreen.tsx- 帖子搜索PostDetailScreen.tsx- 帖子详情(评论列表)MessageListScreen.tsx- 会话列表ChatScreen.tsx- 消息列表NotificationsScreen.tsx- 通知列表GroupMembersScreen.tsx- 群组成员JoinGroupScreen.tsx- 群组列表
三、游标分页类型定义设计
3.1 新增游标分页相关类型 (src/infrastructure/pagination/types.ts)
// ==================== 游标分页类型 ====================
/**
* 游标分页方向
*/
export type CursorDirection = 'forward' | 'backward';
/**
* 游标分页请求参数
*/
export interface CursorPageRequest {
cursor?: string | null;
direction?: CursorDirection;
page_size?: number;
}
/**
* 游标分页响应
*/
export interface CursorPageResponse<T> {
items: T[];
next_cursor: string | null;
prev_cursor: string | null;
has_more: boolean;
}
/**
* 游标分页状态 - 扩展现有 PaginationState
*/
export interface CursorPaginationState {
// 游标相关
nextCursor: string | null;
prevCursor: string | null;
currentCursor: string | null;
// 方向
direction: CursorDirection;
// 通用
hasMore: boolean;
isLoading: boolean;
hasError: boolean;
error?: string;
}
/**
* 游标分页配置
*/
export interface CursorPaginationConfig {
pageSize: number;
prefetchThreshold: number;
enablePrefetch: boolean;
maxRetries: number;
retryDelay: number;
}
/**
* 游标分页加载函数
*/
export type CursorFetchFunction<T = any> = (
request: CursorPageRequest
) => Promise<CursorPageResponse<T>>;
/**
* 游标分页加载结果
*/
export interface CursorLoadMoreResult<T = any> {
success: boolean;
data: T[];
hasMore: boolean;
nextCursor: string | null;
prevCursor: string | null;
fromCache: boolean;
error?: string;
}
/**
* 游标分页刷新结果
*/
export interface CursorRefreshResult<T = any> {
success: boolean;
data: T[];
hasMore: boolean;
nextCursor: string | null;
error?: string;
}
3.2 新增游标分页 DTO 类型 (src/types/dto.ts)
// ==================== 游标分页 DTO ====================
/**
* 游标分页响应基础结构
*/
export interface CursorPaginatedResponse<T> {
items: T[];
next_cursor: string | null;
prev_cursor: string | null;
has_more: boolean;
}
/**
* 帖子列表游标分页响应
*/
export interface PostCursorResponse extends CursorPaginatedResponse<PostDTO> {}
/**
* 会话列表游标分页响应
*/
export interface ConversationCursorResponse extends CursorPaginatedResponse<ConversationResponse> {}
/**
* 消息列表游标分页响应
*/
export interface MessageCursorResponse extends CursorPaginatedResponse<MessageResponse> {}
/**
* 评论列表游标分页响应
*/
export interface CommentCursorResponse extends CursorPaginatedResponse<CommentDTO> {}
/**
* 通知列表游标分页响应
*/
export interface NotificationCursorResponse extends CursorPaginatedResponse<NotificationDTO> {}
/**
* 群组列表游标分页响应
*/
export interface GroupCursorResponse extends CursorPaginatedResponse<GroupResponse> {}
/**
* 群成员列表游标分页响应
*/
export interface GroupMemberCursorResponse extends CursorPaginatedResponse<GroupMemberResponse> {}
/**
* 群公告列表游标分页响应
*/
export interface GroupAnnouncementCursorResponse extends CursorPaginatedResponse<GroupAnnouncementResponse> {}
四、API 服务层改造设计
4.1 新增游标分页 API 方法
postService.ts 改造
class PostService {
// ==================== 游标分页方法 ====================
/**
* 获取帖子列表(游标分页)
* GET /api/v1/posts/cursor
*/
async getPostsCursor(
request: CursorPageRequest = {}
): Promise<CursorPageResponse<Post>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<PostCursorResponse>('/posts/cursor', params);
return {
items: response.data.items,
next_cursor: response.data.next_cursor,
prev_cursor: response.data.prev_cursor,
has_more: response.data.has_more,
};
}
/**
* 搜索帖子(游标分页)
* GET /api/v1/posts/search/cursor
*/
async searchPostsCursor(
keyword: string,
request: CursorPageRequest = {}
): Promise<CursorPageResponse<Post>> {
const params: Record<string, any> = {
keyword,
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<PostCursorResponse>('/posts/search/cursor', params);
return response.data;
}
/**
* 获取用户帖子列表(游标分页)
* GET /api/v1/users/:id/posts/cursor
*/
async getUserPostsCursor(
userId: string,
request: CursorPageRequest = {}
): Promise<CursorPageResponse<Post>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<PostCursorResponse>(
`/users/${userId}/posts/cursor`,
params
);
return response.data;
}
}
messageService.ts 改造
class MessageService {
// ==================== 会话列表游标分页 ====================
/**
* 获取会话列表(游标分页)
* GET /api/v1/conversations/cursor
*/
async getConversationsCursor(
request: CursorPageRequest = {}
): Promise<CursorPageResponse<ConversationResponse>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<ConversationCursorResponse>('/conversations/cursor', params);
return response.data;
}
// ==================== 消息列表游标分页 ====================
/**
* 获取消息列表(游标分页)
* GET /api/v1/conversations/:id/messages/cursor
*/
async getMessagesCursor(
conversationId: string,
request: CursorPageRequest = {}
): Promise<CursorPageResponse<MessageResponse>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<MessageCursorResponse>(
`/conversations/${encodeURIComponent(conversationId)}/messages/cursor`,
params
);
return response.data;
}
}
新增 commentService.ts
// 新建文件: src/services/commentService.ts
import { api } from './api';
import { CommentCursorResponse, CursorPageRequest, CursorPageResponse } from '../types/dto';
import { CommentDTO } from '../types/dto';
class CommentService {
/**
* 获取帖子评论列表(游标分页)
* GET /api/v1/comments/post/:id/cursor
*/
async getPostCommentsCursor(
postId: string,
request: CursorPageRequest = {}
): Promise<CursorPageResponse<CommentDTO>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<CommentCursorResponse>(
`/comments/post/${postId}/cursor`,
params
);
return response.data;
}
/**
* 获取评论回复列表(游标分页)
* GET /api/v1/comments/:id/replies/cursor
*/
async getCommentRepliesCursor(
commentId: string,
request: CursorPageRequest = {}
): Promise<CursorPageResponse<CommentDTO>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<CommentCursorResponse>(
`/comments/${commentId}/replies/cursor`,
params
);
return response.data;
}
}
export const commentService = new CommentService();
新增 notificationService.ts
// 新建文件: src/services/notificationService.ts
import { api } from './api';
import { NotificationCursorResponse, CursorPageRequest, CursorPageResponse } from '../types/dto';
import { NotificationDTO } from '../types/dto';
class NotificationService {
/**
* 获取通知列表(游标分页)
* GET /api/v1/notifications/cursor
*/
async getNotificationsCursor(
request: CursorPageRequest = {}
): Promise<CursorPageResponse<NotificationDTO>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<NotificationCursorResponse>('/notifications/cursor', params);
return response.data;
}
}
export const notificationService = new NotificationService();
新增 groupService.ts (扩展现有群组服务)
// 在现有 groupService.ts 中添加游标分页方法
class GroupService {
/**
* 获取群组列表(游标分页)
* GET /api/v1/groups/cursor
*/
async getGroupsCursor(
request: CursorPageRequest = {}
): Promise<CursorPageResponse<GroupResponse>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<GroupCursorResponse>('/groups/cursor', params);
return response.data;
}
/**
* 获取群成员列表(游标分页)
* GET /api/v1/groups/:id/members/cursor
*/
async getGroupMembersCursor(
groupId: string,
request: CursorPageRequest = {}
): Promise<CursorPageResponse<GroupMemberResponse>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<GroupMemberCursorResponse>(
`/groups/${groupId}/members/cursor`,
params
);
return response.data;
}
/**
* 获取群公告列表(游标分页)
* GET /api/v1/groups/:id/announcements/cursor
*/
async getGroupAnnouncementsCursor(
groupId: string,
request: CursorPageRequest = {}
): Promise<CursorPageResponse<GroupAnnouncementResponse>> {
const params: Record<string, any> = {
page_size: request.page_size || 20,
};
if (request.cursor) params.cursor = request.cursor;
if (request.direction) params.direction = request.direction;
const response = await api.get<GroupAnnouncementCursorResponse>(
`/groups/${groupId}/announcements/cursor`,
params
);
return response.data;
}
}
五、分页 Hook 改造设计
5.1 新增 useCursorPagination Hook (src/hooks/useCursorPagination.ts)
/**
* 游标分页 Hook
*
* 提供基于游标的分页功能,包括:
* - loadMore: 加载下一页
* - loadPrevious: 加载上一页(双向游标支持)
* - refresh: 刷新数据
* - 状态管理
*/
import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
import { CursorPaginationState, CursorPaginationConfig } from '../infrastructure/pagination/types';
export interface UseCursorPaginationOptions<T = any> {
/** 分页键 */
key: string | null;
/** 数据获取函数 */
fetchFunction: CursorFetchFunction<T>;
/** 分页配置 */
config?: Partial<CursorPaginationConfig>;
/** 初始数据 */
initialData?: T[];
/** 是否自动加载第一页 */
autoLoad?: boolean;
/** 是否启用自动预加载 */
enableAutoPrefetch?: boolean;
}
export interface UseCursorPaginationReturn<T = any> {
/** 当前数据列表 */
data: T[];
/** 是否正在加载 */
isLoading: boolean;
/** 是否正在刷新 */
isRefreshing: boolean;
/** 是否有更多数据 */
hasMore: boolean;
/** 是否有上一页 */
hasPrevious: boolean;
/** 是否有错误 */
hasError: boolean;
/** 错误信息 */
error?: string;
/** 当前游标 */
currentCursor: string | null;
/** 加载下一页 */
loadMore: () => Promise<CursorLoadMoreResult<T>>;
/** 加载上一页 */
loadPrevious: () => Promise<CursorLoadMoreResult<T>>;
/** 刷新数据 */
refresh: () => Promise<CursorRefreshResult<T>>;
/** 重置分页状态 */
reset: () => void;
}
export function useCursorPagination<T = any>(
options: UseCursorPaginationOptions<T>
): UseCursorPaginationReturn<T> {
const {
key,
fetchFunction,
config: userConfig,
initialData = [],
autoLoad = false,
enableAutoPrefetch = true,
} = options;
// 合并配置
const config = useMemo(() => ({
...DEFAULT_CURSOR_PAGINATION_CONFIG,
...userConfig,
}), [userConfig]);
// 数据状态
const [data, setData] = useState<T[]>(initialData);
const [state, setState] = useState<CursorPaginationState>({
nextCursor: null,
prevCursor: null,
currentCursor: null,
direction: 'forward',
hasMore: true,
isLoading: false,
hasError: false,
error: undefined,
});
// Refs
const autoLoadRef = useRef(false);
const isMountedRef = useRef(true);
// 派生的 UI 状态
const isRefreshing = state.isLoading && data.length > 0;
const hasPrevious = state.prevCursor !== null;
/**
* 加载下一页
*/
const loadMore = useCallback(async (): Promise<CursorLoadMoreResult<T>> => {
if (!key) {
return { success: false, data: [], hasMore: false, nextCursor: null, prevCursor: null, fromCache: false, error: 'No pagination key' };
}
try {
setState(prev => ({ ...prev, isLoading: true, hasError: false }));
const response = await fetchFunction({
cursor: state.nextCursor,
direction: 'forward',
page_size: config.pageSize,
});
if (isMountedRef.current) {
setData(prev => [...prev, ...response.items]);
setState({
nextCursor: response.next_cursor,
prevCursor: response.prev_cursor,
currentCursor: response.next_cursor,
direction: 'forward',
hasMore: response.has_more,
isLoading: false,
hasError: false,
});
}
return {
success: true,
data: response.items,
hasMore: response.has_more,
nextCursor: response.next_cursor,
prevCursor: response.prev_cursor,
fromCache: false,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (isMountedRef.current) {
setState(prev => ({ ...prev, isLoading: false, hasError: true, error: errorMessage }));
}
return { success: false, data: [], hasMore: false, nextCursor: null, prevCursor: null, fromCache: false, error: errorMessage };
}
}, [key, fetchFunction, state.nextCursor, config.pageSize]);
/**
* 加载上一页
*/
const loadPrevious = useCallback(async (): Promise<CursorLoadMoreResult<T>> => {
if (!key || !state.prevCursor) {
return { success: false, data: [], hasMore: false, nextCursor: null, prevCursor: null, fromCache: false, error: 'No previous page' };
}
try {
setState(prev => ({ ...prev, isLoading: true, hasError: false }));
const response = await fetchFunction({
cursor: state.prevCursor,
direction: 'backward',
page_size: config.pageSize,
});
if (isMountedRef.current) {
setData(prev => [...response.items, ...prev]);
setState({
nextCursor: response.next_cursor,
prevCursor: response.prev_cursor,
currentCursor: response.prev_cursor,
direction: 'backward',
hasMore: response.has_more,
isLoading: false,
hasError: false,
});
}
return {
success: true,
data: response.items,
hasMore: response.has_more,
nextCursor: response.next_cursor,
prevCursor: response.prev_cursor,
fromCache: false,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (isMountedRef.current) {
setState(prev => ({ ...prev, isLoading: false, hasError: true, error: errorMessage }));
}
return { success: false, data: [], hasMore: false, nextCursor: null, prevCursor: null, fromCache: false, error: errorMessage };
}
}, [key, fetchFunction, state.prevCursor, config.pageSize]);
/**
* 刷新数据
*/
const refresh = useCallback(async (): Promise<CursorRefreshResult<T>> => {
if (!key) {
return { success: false, data: [], hasMore: false, nextCursor: null, error: 'No pagination key' };
}
try {
setState(prev => ({ ...prev, isLoading: true, hasError: false }));
const response = await fetchFunction({
cursor: null,
direction: 'forward',
page_size: config.pageSize,
});
if (isMountedRef.current) {
setData(response.items);
setState({
nextCursor: response.next_cursor,
prevCursor: null,
currentCursor: null,
direction: 'forward',
hasMore: response.has_more,
isLoading: false,
hasError: false,
});
}
return {
success: true,
data: response.items,
hasMore: response.has_more,
nextCursor: response.next_cursor,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (isMountedRef.current) {
setState(prev => ({ ...prev, isLoading: false, hasError: true, error: errorMessage }));
}
return { success: false, data: [], hasMore: false, nextCursor: null, error: errorMessage };
}
}, [key, fetchFunction, config.pageSize]);
/**
* 重置分页状态
*/
const reset = useCallback(() => {
if (isMountedRef.current) {
setData([]);
setState({
nextCursor: null,
prevCursor: null,
currentCursor: null,
direction: 'forward',
hasMore: true,
isLoading: false,
hasError: false,
});
autoLoadRef.current = false;
}
}, []);
// 自动加载第一页
useEffect(() => {
if (!key || !autoLoad || autoLoadRef.current) return;
if (data.length > 0) return;
autoLoadRef.current = true;
refresh();
}, [key, autoLoad, data.length, refresh]);
// 组件卸载时清理
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);
return {
data,
isLoading: state.isLoading,
isRefreshing,
hasMore: state.hasMore,
hasPrevious,
hasError: state.hasError,
error: state.error,
currentCursor: state.currentCursor,
loadMore,
loadPrevious,
refresh,
reset,
};
}
六、各列表页面改造计划
6.1 帖子列表 (HomeScreen.tsx)
// 改造后的分页方式
const {
data: posts,
isLoading,
hasMore,
loadMore,
refresh,
} = useCursorPagination<Post>({
key: 'home-posts',
fetchFunction: async (request) => {
return postService.getPostsCursor(request);
},
config: { pageSize: 20 },
autoLoad: true,
});
// UI 层使用 FlatList/FlashList 的 onEndReached
<FlatList
data={posts}
onEndReached={loadMore}
onEndReachedThreshold={0.3}
refreshing={isLoading && posts.length > 0}
onRefresh={refresh}
/>
6.2 会话列表 (MessageListScreen.tsx)
const {
data: conversations,
isLoading,
hasMore,
loadMore,
refresh,
} = useCursorPagination<ConversationResponse>({
key: 'conversations',
fetchFunction: async (request) => {
return messageService.getConversationsCursor(request);
},
autoLoad: true,
});
6.3 消息列表 (ChatScreen.tsx)
// 聊天消息需要支持双向加载
const {
data: messages,
isLoading,
hasMore,
hasPrevious,
loadMore, // 加载更多历史
loadPrevious, // 加载更新的消息
refresh,
} = useCursorPagination<MessageResponse>({
key: `chat-${conversationId}`,
fetchFunction: async (request) => {
return messageService.getMessagesCursor(conversationId, request);
},
});
6.4 帖子搜索 (SearchScreen.tsx)
const {
data: searchResults,
isLoading,
hasMore,
loadMore,
refresh,
} = useCursorPagination<Post>({
key: `search-${keyword}`,
fetchFunction: async (request) => {
return postService.searchPostsCursor(keyword, request);
},
autoLoad: !!keyword,
});
6.5 评论列表 (PostDetailScreen.tsx)
const {
data: comments,
isLoading,
hasMore,
loadMore,
refresh,
} = useCursorPagination<CommentDTO>({
key: `comments-${postId}`,
fetchFunction: async (request) => {
return commentService.getPostCommentsCursor(postId, request);
},
});
6.6 通知列表 (NotificationsScreen.tsx)
const {
data: notifications,
isLoading,
hasMore,
loadMore,
refresh,
} = useCursorPagination<NotificationDTO>({
key: 'notifications',
fetchFunction: async (request) => {
return notificationService.getNotificationsCursor(request);
},
});
6.7 群组列表 (JoinGroupScreen.tsx)
const {
data: groups,
isLoading,
hasMore,
loadMore,
refresh,
} = useCursorPagination<GroupResponse>({
key: 'groups',
fetchFunction: async (request) => {
return groupService.getGroupsCursor(request);
},
});
6.8 群组成员 (GroupMembersScreen.tsx)
const {
data: members,
isLoading,
hasMore,
loadMore,
refresh,
} = useCursorPagination<GroupMemberResponse>({
key: `group-members-${groupId}`,
fetchFunction: async (request) => {
return groupService.getGroupMembersCursor(groupId, request);
},
});
七、需要修改的文件清单
7.1 新增文件
| 文件路径 | 说明 |
|---|---|
src/hooks/useCursorPagination.ts |
新增游标分页 Hook |
src/services/commentService.ts |
新增评论服务(含游标分页) |
src/services/notificationService.ts |
新增通知服务(含游标分页) |
src/types/cursor.ts |
新增游标分页相关类型定义 |
7.2 需要修改的文件
| 文件路径 | 修改内容 |
|---|---|
src/types/dto.ts |
新增游标分页 DTO 类型 |
src/services/postService.ts |
新增游标分页方法(保留旧方法兼容) |
src/services/messageService.ts |
新增会话/消息游标分页方法 |
src/services/groupService.ts |
新增群组相关游标分页方法 |
src/infrastructure/pagination/types.ts |
新增游标分页类型定义 |
src/infrastructure/pagination/CursorPaginationStateManager.ts |
新增游标分页状态管理器(可选) |
src/screens/home/HomeScreen.tsx |
改造为使用 useCursorPagination |
src/screens/home/SearchScreen.tsx |
改造为使用 useCursorPagination |
src/screens/home/PostDetailScreen.tsx |
改造评论列表使用 useCursorPagination |
src/screens/message/MessageListScreen.tsx |
改造为使用 useCursorPagination |
src/screens/message/ChatScreen.tsx |
改造消息列表使用 useCursorPagination |
src/screens/message/NotificationsScreen.tsx |
改造为使用 useCursorPagination |
src/screens/message/JoinGroupScreen.tsx |
改造为使用 useCursorPagination |
src/screens/message/GroupMembersScreen.tsx |
改造为使用 useCursorPagination |
八、实现顺序建议
阶段一:基础设施(优先级高)
- 在
src/types/dto.ts新增游标分页 DTO 类型 - 在
src/infrastructure/pagination/types.ts新增游标分页类型 - 创建
src/hooks/useCursorPagination.ts
阶段二:API 服务层
- 改造
postService.ts- 新增游标分页方法 - 改造
messageService.ts- 新增游标分页方法 - 创建
commentService.ts - 创建
notificationService.ts - 改造
groupService.ts- 新增游标分页方法
阶段三:UI 组件改造(按依赖顺序)
HomeScreen.tsx- 帖子列表SearchScreen.tsx- 帖子搜索PostDetailScreen.tsx- 评论列表MessageListScreen.tsx- 会话列表ChatScreen.tsx- 消息列表NotificationsScreen.tsx- 通知列表JoinGroupScreen.tsx- 群组列表GroupMembersScreen.tsx- 群组成员
九、架构图
graph TB
subgraph "UI Layer"
HomeScreen[HomeScreen.tsx]
SearchScreen[SearchScreen.tsx]
ChatScreen[ChatScreen.tsx]
MessageListScreen[MessageListScreen.tsx]
NotificationsScreen[NotificationsScreen.tsx]
GroupMembersScreen[GroupMembersScreen.tsx]
end
subgraph "Hooks Layer"
useCursorPagination[useCursorPagination.ts]
usePagination[usePagination.ts - 保留兼容]
end
subgraph "Service Layer"
postService[postService.ts]
messageService[messageService.ts]
commentService[commentService.ts]
notificationService[notificationService.ts]
groupService[groupService.ts]
end
subgraph "API Layer"
API[API Client]
end
subgraph "Backend"
CursorAPI[游标分页 API]
end
HomeScreen --> useCursorPagination
SearchScreen --> useCursorPagination
ChatScreen --> useCursorPagination
MessageListScreen --> useCursorPagination
NotificationsScreen --> useCursorPagination
GroupMembersScreen --> useCursorPagination
useCursorPagination --> postService
useCursorPagination --> messageService
useCursorPagination --> commentService
useCursorPagination --> notificationService
useCursorPagination --> groupService
postService --> API
messageService --> API
commentService --> API
notificationService --> API
groupService --> API
API --> CursorAPI
十、注意事项
10.1 兼容性考虑
- 保留现有的页码分页方法一段时间,避免一次性全量替换带来的风险
- 通过功能开关或配置控制是否启用游标分页
10.2 消息列表特殊处理
- 聊天消息需要支持双向加载(加载历史 + 加载最新)
- 需要处理好新消息的插入位置(头部 vs 尾部)
10.3 缓存策略
- 游标分页的缓存策略需要调整,不再基于页码
- 可以考虑基于 cursor 字符串进行缓存
10.4 错误处理
- 网络错误时需要显示重试选项
- 游标失效时需要提示用户刷新列表