/** * MessageManager React Hooks * * 提供React组件与MessageManager的集成 * 所有hooks都基于Zustand selector机制 * 确保组件能实时获取状态更新 * * 重构说明: * - 移除了手动订阅机制,改用 Zustand selector * - Zustand 会自动处理依赖追踪和组件重渲染 * - 不需要手动管理订阅/取消订阅 */ import { useState, useEffect, useCallback, useRef } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { ConversationResponse, MessageResponse, MessageSegment } from '../types/dto'; import { messageManager } from './messageManager'; import { useMessageStore } from './message'; // ==================== useConversations - 获取会话列表 ==================== interface UseConversationsReturn { conversations: ConversationResponse[]; isLoading: boolean; refresh: () => Promise; loadMore: () => Promise; hasMore: boolean; } /** * 获取会话列表 * 使用 Zustand selector 自动订阅状态变化 */ export function useConversations(): UseConversationsReturn { // 使用 Zustand selector 直接订阅状态 const conversations = useMessageStore(state => state.conversationList); const isLoading = useMessageStore(state => state.isLoadingConversations); const [hasMore, setHasMore] = useState(() => messageManager.canLoadMoreConversations()); useEffect(() => { // 初始化时确保 MessageManager 已初始化 messageManager.initialize().catch(error => { console.error('[useConversations] 初始化失败:', error); }); // 冷启动兜底:延迟做一次强制刷新,避免首次因时序问题拿到空列表 const coldStartSyncTimer = setTimeout(() => { messageManager.refreshConversations(true, 'hooks-initial-refresh').catch(error => { console.error('[useConversations] 冷启动强制刷新失败:', error); }); }, 1200); return () => { clearTimeout(coldStartSyncTimer); }; }, []); // 监听 hasMore 变化 useEffect(() => { setHasMore(messageManager.canLoadMoreConversations()); }, [conversations]); const refresh = useCallback(async () => { await messageManager.refreshConversations(true, 'hooks-manual-refresh'); }, []); const loadMore = useCallback(async () => { await messageManager.loadMoreConversations(); }, []); return { conversations, isLoading, refresh, loadMore, hasMore, }; } // ==================== useMessages - 获取指定会话的消息 ==================== interface UseMessagesReturn { messages: MessageResponse[]; isLoading: boolean; hasMore: boolean; loadMore: () => Promise; refresh: () => Promise; } /** * 获取指定会话的消息 * 使用 Zustand selector 自动订阅消息变化 */ export function useMessages(conversationId: string | null): UseMessagesReturn { const normalizedConversationId = conversationId ? String(conversationId) : null; // 使用 useShallow 避免每次返回新数组引用导致的无限循环 const messages = useMessageStore( useShallow(state => normalizedConversationId ? (state.messagesMap.get(normalizedConversationId) || []) : [] ) ); const isLoadingMessages = useMessageStore(state => normalizedConversationId ? state.loadingMessagesSet.has(normalizedConversationId) : false ); const [hasMore, setHasMore] = useState(true); const loadMoreInFlightRef = useRef(false); useEffect(() => { if (!normalizedConversationId) { return; } // 架构入口:激活会话并完成初始化/同步 messageManager.activateConversation(normalizedConversationId).catch(error => { console.error('[useMessages] 激活会话失败:', error); }); return () => { // 清理活动会话 if (messageManager.getActiveConversation() === normalizedConversationId) { messageManager.setActiveConversation(null); } }; }, [normalizedConversationId]); const loadMore = useCallback(async () => { if (!conversationId || !hasMore || loadMoreInFlightRef.current) return; const currentMessages = messageManager.getMessages(conversationId); if (currentMessages.length === 0) return; // 消息数组在 MessageManager 内保持 seq 升序,首项即最早消息 const minSeq = currentMessages[0]?.seq ?? Math.min(...currentMessages.map(m => m.seq)); loadMoreInFlightRef.current = true; const loadedMessages = await messageManager.loadMoreMessages(conversationId, minSeq, 20); loadMoreInFlightRef.current = false; // 如果没有加载到新消息,说明没有更多了 if (loadedMessages.length === 0) { setHasMore(false); } }, [conversationId, hasMore]); const refresh = useCallback(async () => { if (!conversationId) return; await messageManager.fetchMessages(conversationId); setHasMore(true); }, [conversationId]); return { messages, isLoading: isLoadingMessages, hasMore, loadMore, refresh, }; } // ==================== useSendMessage - 发送消息 ==================== interface UseSendMessageReturn { sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise; isSending: boolean; } /** * 发送消息 */ export function useSendMessage(conversationId: string | null): UseSendMessageReturn { const [isSending, setIsSending] = useState(false); const sendMessage = useCallback( async (segments: MessageSegment[], options?: { replyToId?: string }): Promise => { if (!conversationId) return null; setIsSending(true); try { const message = await messageManager.sendMessage(conversationId, segments, options); return message; } finally { setIsSending(false); } }, [conversationId] ); return { sendMessage, isSending, }; } // ==================== useMarkAsRead - 标记已读 ==================== interface UseMarkAsReadReturn { markAsRead: (seq: number) => Promise; markAllAsRead: () => Promise; isMarking: boolean; } /** * 标记已读 */ export function useMarkAsRead(conversationId: string | null): UseMarkAsReadReturn { const [isMarking, setIsMarking] = useState(false); const markAsRead = useCallback( async (seq: number) => { if (!conversationId) return; setIsMarking(true); try { await messageManager.markAsRead(conversationId, seq); } finally { setIsMarking(false); } }, [conversationId] ); const markAllAsRead = useCallback(async () => { setIsMarking(true); try { await messageManager.markAllAsRead(); } finally { setIsMarking(false); } }, []); return { markAsRead, markAllAsRead, isMarking, }; } // ==================== useUnreadCount - 获取未读数 ==================== interface UseUnreadCountReturn { totalUnreadCount: number; systemUnreadCount: number; } /** * 获取未读消息数 * 使用 Zustand selector 直接订阅未读数变化 */ export function useUnreadCount(): UseUnreadCountReturn { // 使用 Zustand selector 直接订阅未读数 const totalUnreadCount = useMessageStore(state => state.totalUnreadCount); const systemUnreadCount = useMessageStore(state => state.systemUnreadCount); useEffect(() => { // 初始化时获取最新未读数 messageManager.fetchUnreadCount(); }, []); return { totalUnreadCount, systemUnreadCount, }; } // ==================== useTotalUnreadCount - 获取总未读数(包含系统消息) ==================== /** * 获取总未读消息数(会话未读 + 系统消息未读) * 使用 Zustand selector 直接订阅 */ export function useTotalUnreadCount(): number { const totalUnreadCount = useMessageStore(state => state.totalUnreadCount); const systemUnreadCount = useMessageStore(state => state.systemUnreadCount); useEffect(() => { // 初始化时获取最新未读数 messageManager.fetchUnreadCount(); }, []); return totalUnreadCount + systemUnreadCount; } // ==================== useConversation - 获取单个会话信息 ==================== interface UseConversationReturn { conversation: ConversationResponse | null; isLoading: boolean; refresh: () => Promise; } /** * 获取单个会话信息 * 使用 Zustand selector 直接订阅会话变化 */ export function useConversation(conversationId: string | null): UseConversationReturn { // 使用 useShallow 避免每次返回新对象引用导致的无限循环 const conversation = useMessageStore( useShallow(state => conversationId ? (state.conversations.get(conversationId) || null) : null ) ); const [isLoading, setIsLoading] = useState(false); const refresh = useCallback(async () => { if (!conversationId) return; setIsLoading(true); await messageManager.fetchConversationDetail(conversationId); setIsLoading(false); }, [conversationId]); return { conversation, isLoading, refresh, }; } // ==================== useMessageManager - 通用状态 ==================== interface UseMessageManagerReturn { isConnected: boolean; isInitialized: boolean; } /** * 通用MessageManager状态 * 使用 Zustand selector 直接订阅连接状态 */ export function useMessageManager(): UseMessageManagerReturn { // 使用 Zustand selector 直接订阅状态 const isConnected = useMessageStore(state => state.isWSConnected); const isInitialized = useMessageStore(state => state.isInitialized); useEffect(() => { // 初始化 messageManager.initialize().catch(error => { console.error('[useMessageManager] 初始化失败:', error); }); }, []); return { isConnected, isInitialized, }; } // ==================== useCreateConversation - 创建会话 ==================== interface UseCreateConversationReturn { createConversation: (userId: string) => Promise; isCreating: boolean; } /** * 创建私聊会话 */ export function useCreateConversation(): UseCreateConversationReturn { const [isCreating, setIsCreating] = useState(false); const createConversation = useCallback(async (userId: string): Promise => { setIsCreating(true); try { const conversation = await messageManager.createConversation(userId); return conversation; } finally { setIsCreating(false); } }, []); return { createConversation, isCreating, }; } // ==================== useUpdateConversation - 更新会话 ==================== interface UseUpdateConversationReturn { updateConversation: (conversationId: string, updates: Partial) => void; } /** * 本地更新会话信息 */ export function useUpdateConversation(): UseUpdateConversationReturn { const updateConversation = useCallback((conversationId: string, updates: Partial) => { messageManager.updateConversation(conversationId, updates); }, []); return { updateConversation, }; } // ==================== useSystemUnreadCount - 系统消息未读数操作 ==================== interface UseSystemUnreadCountReturn { setSystemUnreadCount: (count: number) => void; incrementSystemUnreadCount: () => void; decrementSystemUnreadCount: (count?: number) => void; } /** * 系统消息未读数操作 */ export function useSystemUnreadCount(): UseSystemUnreadCountReturn { const setSystemUnreadCount = useCallback((count: number) => { messageManager.setSystemUnreadCount(count); }, []); const incrementSystemUnreadCount = useCallback(() => { messageManager.incrementSystemUnreadCount(); }, []); const decrementSystemUnreadCount = useCallback((count?: number) => { messageManager.decrementSystemUnreadCount(count); }, []); return { setSystemUnreadCount, incrementSystemUnreadCount, decrementSystemUnreadCount, }; } // ==================== useMessageListRefresh - MessageListScreen焦点刷新 ==================== interface UseMessageListRefreshReturn { refresh: () => Promise; isRefreshing: boolean; } /** * MessageListScreen焦点刷新Hook */ export function useMessageListRefresh(): UseMessageListRefreshReturn { const [isRefreshing, setIsRefreshing] = useState(false); const lastRefreshTimeRef = useRef(0); const refresh = useCallback(async () => { // 防止重复刷新(最小间隔500ms) const now = Date.now(); if (now - lastRefreshTimeRef.current < 500) { return; } lastRefreshTimeRef.current = now; setIsRefreshing(true); try { await messageManager.refreshConversations(true, 'hooks-load-more-fallback'); } catch (error) { console.error('[useMessageListRefresh] 刷新失败:', error); } finally { setIsRefreshing(false); } }, []); return { refresh, isRefreshing, }; } // ==================== 便捷hooks组合 ==================== /** * ChatScreen专用Hook组合 */ interface UseChatReturn { // 消息相关 messages: MessageResponse[]; isLoadingMessages: boolean; hasMoreMessages: boolean; loadMoreMessages: () => Promise; refreshMessages: () => Promise; // 发送消息 sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise; isSending: boolean; // 已读相关 markAsRead: (seq: number) => Promise; // 会话信息 conversation: ConversationResponse | null; } export function useChat(conversationId: string | null): UseChatReturn { const { messages, isLoading: isLoadingMessages, hasMore: hasMoreMessages, loadMore, refresh } = useMessages(conversationId); const { sendMessage, isSending } = useSendMessage(conversationId); const { markAsRead } = useMarkAsRead(conversationId); const { conversation } = useConversation(conversationId); return { messages, isLoadingMessages, hasMoreMessages, loadMoreMessages: loadMore, refreshMessages: refresh, sendMessage, isSending, markAsRead, conversation, }; } // ==================== useGroupTyping - 群聊输入状态 ==================== interface UseGroupTypingReturn { typingUsers: string[]; } /** * 获取群聊输入状态 * 使用 Zustand selector 直接订阅 */ export function useGroupTyping(groupId: string | null): UseGroupTypingReturn { // 使用 useShallow 避免每次返回新数组引用导致的无限循环 const typingUsers = useMessageStore( useShallow(state => groupId ? (state.typingUsersMap.get(groupId) || []) : [] ) ); return { typingUsers }; } // ==================== useGroupMuted - 群聊禁言状态 ==================== interface UseGroupMutedReturn { isMuted: boolean; setMuted: (muted: boolean) => void; } /** * 获取群聊禁言状态 * 使用 Zustand selector 直接订阅 */ export function useGroupMuted(groupId: string | null, currentUserId?: string): UseGroupMutedReturn { // 使用 Zustand selector 直接订阅禁言状态 const isMuted = useMessageStore(state => groupId ? (state.mutedStatusMap.get(groupId) || false) : false ); const setMuted = useCallback((muted: boolean) => { if (groupId) { messageManager.setMutedStatus(groupId, muted); } }, [groupId]); return { isMuted, setMuted }; } /** * MessageListScreen专用Hook组合 */ interface UseMessageListReturn { conversations: ConversationResponse[]; isLoading: boolean; refresh: () => Promise; loadMore: () => Promise; hasMore: boolean; totalUnreadCount: number; systemUnreadCount: number; markAllAsRead: () => Promise; } export function useMessageList(): UseMessageListReturn { const { conversations, isLoading, refresh, loadMore, hasMore } = useConversations(); const { totalUnreadCount, systemUnreadCount } = useUnreadCount(); const { markAllAsRead } = useMarkAsRead(null); return { conversations, isLoading, refresh, loadMore, hasMore, totalUnreadCount, systemUnreadCount, markAllAsRead, }; }