350 lines
8.0 KiB
TypeScript
350 lines
8.0 KiB
TypeScript
|
|
/**
|
|||
|
|
* 数据预取Hook
|
|||
|
|
* 用于关键数据的预加载,提升用户体验
|
|||
|
|
*
|
|||
|
|
* 特性:
|
|||
|
|
* - 应用启动时预取关键数据
|
|||
|
|
* - 页面导航时预取相关数据
|
|||
|
|
* - 智能预取策略(基于用户行为)
|
|||
|
|
* - 并发控制和优先级管理
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { useCallback, useEffect, useRef } from 'react';
|
|||
|
|
import { postManager } from '../stores/postManager';
|
|||
|
|
import { groupManager } from '../stores/groupManager';
|
|||
|
|
import { userManager } from '../stores/userManager';
|
|||
|
|
import { messageManager } from '../stores/messageManager';
|
|||
|
|
|
|||
|
|
// ==================== 预取配置 ====================
|
|||
|
|
|
|||
|
|
/** 预取任务优先级 */
|
|||
|
|
enum Priority {
|
|||
|
|
HIGH = 0,
|
|||
|
|
MEDIUM = 1,
|
|||
|
|
LOW = 2,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 预取任务接口 */
|
|||
|
|
interface PrefetchTask {
|
|||
|
|
key: string;
|
|||
|
|
executor: () => Promise<any>;
|
|||
|
|
priority: Priority;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 预取服务类 */
|
|||
|
|
class PrefetchService {
|
|||
|
|
/** 预取任务队列 */
|
|||
|
|
private queue: PrefetchTask[] = [];
|
|||
|
|
|
|||
|
|
/** 是否正在处理队列 */
|
|||
|
|
private isProcessing = false;
|
|||
|
|
|
|||
|
|
/** 最大并发数 */
|
|||
|
|
private readonly MAX_CONCURRENCY = 3;
|
|||
|
|
|
|||
|
|
/** 当前活跃的任务数 */
|
|||
|
|
private activeCount = 0;
|
|||
|
|
|
|||
|
|
/** 是否启用调试日志 */
|
|||
|
|
private readonly DEBUG = __DEV__ || false;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加预取任务到队列
|
|||
|
|
* @param task 预取任务
|
|||
|
|
*/
|
|||
|
|
schedule(task: PrefetchTask): void {
|
|||
|
|
// 检查是否已有相同key的任务
|
|||
|
|
const existingIndex = this.queue.findIndex(t => t.key === task.key);
|
|||
|
|
if (existingIndex >= 0) {
|
|||
|
|
// 如果新任务优先级更高,替换旧任务
|
|||
|
|
if (task.priority < this.queue[existingIndex].priority) {
|
|||
|
|
this.queue[existingIndex] = task;
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按优先级插入队列
|
|||
|
|
let insertIndex = this.queue.findIndex(t => t.priority > task.priority);
|
|||
|
|
if (insertIndex === -1) {
|
|||
|
|
insertIndex = this.queue.length;
|
|||
|
|
}
|
|||
|
|
this.queue.splice(insertIndex, 0, task);
|
|||
|
|
|
|||
|
|
this.processQueue();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理预取队列
|
|||
|
|
*/
|
|||
|
|
private async processQueue(): Promise<void> {
|
|||
|
|
if (this.isProcessing || this.queue.length === 0) return;
|
|||
|
|
|
|||
|
|
this.isProcessing = true;
|
|||
|
|
|
|||
|
|
while (this.queue.length > 0 && this.activeCount < this.MAX_CONCURRENCY) {
|
|||
|
|
const task = this.queue.shift();
|
|||
|
|
if (!task) break;
|
|||
|
|
|
|||
|
|
this.activeCount++;
|
|||
|
|
|
|||
|
|
// 异步执行任务
|
|||
|
|
this.executeTask(task).finally(() => {
|
|||
|
|
this.activeCount--;
|
|||
|
|
this.processQueue();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.isProcessing = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 执行单个预取任务
|
|||
|
|
*/
|
|||
|
|
private async executeTask(task: PrefetchTask): Promise<void> {
|
|||
|
|
try {
|
|||
|
|
await task.executor();
|
|||
|
|
} catch (error) {
|
|||
|
|
if (this.DEBUG) {
|
|||
|
|
console.warn(`[Prefetch] 任务失败: ${task.key}`, error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清空预取队列
|
|||
|
|
*/
|
|||
|
|
clearQueue(): void {
|
|||
|
|
this.queue = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取队列长度
|
|||
|
|
*/
|
|||
|
|
getQueueLength(): number {
|
|||
|
|
return this.queue.length;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 全局预取服务实例 */
|
|||
|
|
const prefetchService = new PrefetchService();
|
|||
|
|
|
|||
|
|
// ==================== 预取函数 ====================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 预取帖子数据
|
|||
|
|
* @param types 帖子类型数组
|
|||
|
|
*/
|
|||
|
|
function prefetchPosts(types: string[] = ['recommend', 'hot']): void {
|
|||
|
|
types.forEach((type, index) => {
|
|||
|
|
prefetchService.schedule({
|
|||
|
|
key: `posts:${type}:1`,
|
|||
|
|
executor: () => postManager.getPosts(type, 1, 20),
|
|||
|
|
priority: index === 0 ? Priority.HIGH : Priority.MEDIUM,
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 预取会话数据
|
|||
|
|
*/
|
|||
|
|
function prefetchConversations(): void {
|
|||
|
|
prefetchService.schedule({
|
|||
|
|
key: 'conversations:list',
|
|||
|
|
executor: async () => {
|
|||
|
|
await messageManager.initialize();
|
|||
|
|
await messageManager.fetchConversations(true);
|
|||
|
|
return messageManager.getConversations();
|
|||
|
|
},
|
|||
|
|
priority: Priority.HIGH,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 同时预取未读数
|
|||
|
|
prefetchService.schedule({
|
|||
|
|
key: 'conversations:unread',
|
|||
|
|
executor: async () => {
|
|||
|
|
await messageManager.initialize();
|
|||
|
|
await messageManager.fetchUnreadCount();
|
|||
|
|
return messageManager.getUnreadCount();
|
|||
|
|
},
|
|||
|
|
priority: Priority.HIGH,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 预取用户信息
|
|||
|
|
*/
|
|||
|
|
function prefetchUserInfo(): void {
|
|||
|
|
prefetchService.schedule({
|
|||
|
|
key: 'users:me',
|
|||
|
|
executor: () => userManager.getCurrentUser(),
|
|||
|
|
priority: Priority.HIGH,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 预取会话详情
|
|||
|
|
* @param conversationIds 会话ID数组
|
|||
|
|
*/
|
|||
|
|
function prefetchConversationDetails(conversationIds: string[]): void {
|
|||
|
|
conversationIds.forEach(id => {
|
|||
|
|
prefetchService.schedule({
|
|||
|
|
key: `conversations:detail:${id}`,
|
|||
|
|
executor: async () => {
|
|||
|
|
await messageManager.initialize();
|
|||
|
|
return messageManager.fetchConversationDetail(id);
|
|||
|
|
},
|
|||
|
|
priority: Priority.LOW,
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 预取群组成员
|
|||
|
|
* @param groupIds 群组ID数组
|
|||
|
|
*/
|
|||
|
|
function prefetchGroupMembers(groupIds: string[]): void {
|
|||
|
|
groupIds.forEach(id => {
|
|||
|
|
prefetchService.schedule({
|
|||
|
|
key: `groups:members:${id}`,
|
|||
|
|
executor: async () => {
|
|||
|
|
const response = await groupManager.getMembers(id, 1, 50);
|
|||
|
|
return response.list || [];
|
|||
|
|
},
|
|||
|
|
priority: Priority.LOW,
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 应用启动时预取
|
|||
|
|
* 预取最关键的数据
|
|||
|
|
*/
|
|||
|
|
function prefetchOnAppLaunch(): void {
|
|||
|
|
// 高优先级:用户信息
|
|||
|
|
prefetchUserInfo();
|
|||
|
|
|
|||
|
|
// 高优先级:会话列表和未读数
|
|||
|
|
prefetchConversations();
|
|||
|
|
|
|||
|
|
// 中优先级:帖子列表
|
|||
|
|
prefetchPosts(['recommend']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 进入消息页面时预取
|
|||
|
|
*/
|
|||
|
|
function prefetchMessageScreen(): void {
|
|||
|
|
// 获取当前会话列表,预取前几个会话的详情
|
|||
|
|
const conversations = messageManager.getConversations();
|
|||
|
|
const topConversationIds = conversations.slice(0, 5).map((c: any) => c.id);
|
|||
|
|
|
|||
|
|
// 预取会话详情
|
|||
|
|
prefetchConversationDetails(topConversationIds);
|
|||
|
|
|
|||
|
|
// 预取群组成员(如果是群聊)
|
|||
|
|
const groupIds = conversations
|
|||
|
|
.filter((c: any) => c.type === 'group' && c.group?.id)
|
|||
|
|
.slice(0, 3)
|
|||
|
|
.map((c: any) => String(c.group!.id));
|
|||
|
|
|
|||
|
|
prefetchGroupMembers(groupIds);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 进入首页时预取
|
|||
|
|
*/
|
|||
|
|
function prefetchHomeScreen(): void {
|
|||
|
|
prefetchPosts(['recommend', 'hot', 'latest']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== React Hook ====================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 数据预取Hook
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* ```tsx
|
|||
|
|
* const { prefetchOnAppLaunch, prefetchMessageScreen } = usePrefetch();
|
|||
|
|
*
|
|||
|
|
* useEffect(() => {
|
|||
|
|
* prefetchOnAppLaunch();
|
|||
|
|
* }, []);
|
|||
|
|
* ```
|
|||
|
|
*/
|
|||
|
|
export function usePrefetch() {
|
|||
|
|
/** 是否已完成初始预取 */
|
|||
|
|
const hasInitialPrefetched = useRef(false);
|
|||
|
|
|
|||
|
|
/** 预取帖子 */
|
|||
|
|
const prefetchPostsData = useCallback((types?: string[]) => {
|
|||
|
|
prefetchPosts(types);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
/** 预取会话 */
|
|||
|
|
const prefetchConversationsData = useCallback(() => {
|
|||
|
|
prefetchConversations();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
/** 预取用户信息 */
|
|||
|
|
const prefetchUserData = useCallback(() => {
|
|||
|
|
prefetchUserInfo();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
/** 应用启动预取 */
|
|||
|
|
const doPrefetchOnAppLaunch = useCallback(() => {
|
|||
|
|
if (hasInitialPrefetched.current) return;
|
|||
|
|
hasInitialPrefetched.current = true;
|
|||
|
|
prefetchOnAppLaunch();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
/** 消息页面预取 */
|
|||
|
|
const doPrefetchMessageScreen = useCallback(() => {
|
|||
|
|
prefetchMessageScreen();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
/** 首页预取 */
|
|||
|
|
const doPrefetchHomeScreen = useCallback(() => {
|
|||
|
|
prefetchHomeScreen();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
/** 清空预取队列 */
|
|||
|
|
const clearPrefetchQueue = useCallback(() => {
|
|||
|
|
prefetchService.clearQueue();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
/** 获取队列长度 */
|
|||
|
|
const getQueueLength = useCallback(() => {
|
|||
|
|
return prefetchService.getQueueLength();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
// 基础预取方法
|
|||
|
|
prefetchPosts: prefetchPostsData,
|
|||
|
|
prefetchConversations: prefetchConversationsData,
|
|||
|
|
prefetchUserInfo: prefetchUserData,
|
|||
|
|
|
|||
|
|
// 场景预取方法
|
|||
|
|
prefetchOnAppLaunch: doPrefetchOnAppLaunch,
|
|||
|
|
prefetchMessageScreen: doPrefetchMessageScreen,
|
|||
|
|
prefetchHomeScreen: doPrefetchHomeScreen,
|
|||
|
|
|
|||
|
|
// 工具方法
|
|||
|
|
clearPrefetchQueue,
|
|||
|
|
getQueueLength,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 导出 ====================
|
|||
|
|
|
|||
|
|
export {
|
|||
|
|
prefetchPosts,
|
|||
|
|
prefetchConversations,
|
|||
|
|
prefetchUserInfo,
|
|||
|
|
prefetchOnAppLaunch,
|
|||
|
|
prefetchMessageScreen,
|
|||
|
|
prefetchHomeScreen,
|
|||
|
|
prefetchService,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default usePrefetch;
|