/** * 消息模块 React Hooks * * 提供React组件与消息状态管理的集成 * 所有hooks都基于zustand store的selector机制 * 确保组件能实时获取状态更新 * * 重构说明: * - 移除了 SubscriptionManager,改用 Zustand selector * - Zustand 会自动处理依赖追踪和组件重渲染 * - 不需要手动管理订阅/取消订阅 */ import { useState, useEffect, useCallback, useRef } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto'; import { useMessageStore, normalizeConversationId } from './store'; // ==================== useConversations - 获取会话列表 ==================== interface UseConversationsReturn { conversations: ConversationResponse[]; isLoading: boolean; refresh: () => Promise; loadMore: () => Promise; hasMore: boolean; } /** * 获取会话列表 * 用于 MessageListScreen 等需要显示会话列表的组件 * 使用 Zustand selector 自动订阅状态变化 */ export function useConversations( fetchConversations: (forceRefresh: boolean, source: string) => Promise, loadMoreConversations: () => Promise, canLoadMore: () => boolean, initialize: () => Promise ): UseConversationsReturn { // 使用 Zustand selector 直接订阅状态 const conversations = useMessageStore(state => state.conversationList); const isLoading = useMessageStore(state => state.isLoadingConversations); const [hasMore, setHasMore] = useState(() => canLoadMore()); useEffect(() => { // 初始化 initialize().catch(error => { console.error('[useConversations] 初始化失败:', error); }); // 冷启动兜底:延迟做一次强制刷新 const coldStartSyncTimer = setTimeout(() => { fetchConversations(true, 'hooks-initial-refresh').catch(error => { console.error('[useConversations] 冷启动强制刷新失败:', error); }); }, 1200); return () => { clearTimeout(coldStartSyncTimer); }; }, []); // 监听 hasMore 变化 useEffect(() => { setHasMore(canLoadMore()); }, [conversations, canLoadMore]); const refresh = useCallback(async () => { await fetchConversations(true, 'hooks-manual-refresh'); }, [fetchConversations]); const loadMore = useCallback(async () => { await loadMoreConversations(); }, [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, fetchMessages: (conversationId: string) => Promise, loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise ): UseMessagesReturn { const normalizedConversationId = normalizeConversationId(conversationId); const store = useMessageStore(); // 使用 useShallow 避免每次返回新数组引用导致的无限循环 const messages = useMessageStore( useShallow(state => state.messagesMap.get(normalizedConversationId) || []) ); const isLoadingMessages = useMessageStore(state => state.loadingMessagesSet.has(normalizedConversationId)); const [hasMore, setHasMore] = useState(true); useEffect(() => { // 激活会话 fetchMessages(normalizedConversationId).catch(error => { console.error('[useMessages] 激活会话失败:', error); }); return () => { // 清理活动会话 if (store.getActiveConversation() === normalizedConversationId) { store.setCurrentConversation(null); } }; }, [normalizedConversationId]); const loadMore = useCallback(async () => { const currentMessages = messages; if (currentMessages.length === 0) return; // 消息数组在 store 内保持 seq 升序,首项即最早消息 const minSeq = currentMessages[0]?.seq; if (minSeq === undefined) return; const loadedMessages = await loadMoreMessages(conversationId, minSeq, 20); setHasMore(loadedMessages.length > 0); }, [conversationId, messages, loadMoreMessages]); const refresh = useCallback(async () => { await fetchMessages(conversationId); }, [conversationId, fetchMessages]); return { messages, isLoading: isLoadingMessages, hasMore, loadMore, refresh, }; } // ==================== useSendMessage - 发送消息 ==================== interface UseSendMessageReturn { sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise; isSending: boolean; } /** * 发送消息 */ export function useSendMessage( conversationId: string, sendMessageService: (conversationId: string, segments: MessageSegment[], options?: { replyToId?: string }) => Promise ): UseSendMessageReturn { const [isSending, setIsSending] = useState(false); const sendMessage = useCallback(async (segments: MessageSegment[], options?: { replyToId?: string }) => { setIsSending(true); try { const message = await sendMessageService(conversationId, segments, options); return message; } catch (error) { console.error('[useSendMessage] 发送消息失败:', error); return null; } finally { setIsSending(false); } }, [conversationId, sendMessageService]); return { sendMessage, isSending, }; } // ==================== useMarkAsRead - 标记已读 ==================== interface UseMarkAsReadReturn { markAsRead: (seq: number) => Promise; markAllAsRead: () => Promise; } /** * 标记已读 */ export function useMarkAsRead( conversationId: string, markAsReadService: (conversationId: string, seq: number) => Promise, markAllAsReadService: () => Promise ): UseMarkAsReadReturn { const markAsRead = useCallback(async (seq: number) => { await markAsReadService(conversationId, seq); }, [conversationId, markAsReadService]); const markAllAsRead = useCallback(async () => { await markAllAsReadService(); }, [markAllAsReadService]); return { markAsRead, markAllAsRead, }; } // ==================== useUnreadCount - 获取未读数 ==================== interface UseUnreadCountReturn { totalUnreadCount: number; systemUnreadCount: number; refresh: () => Promise; } /** * 获取未读数 * 使用 Zustand selector 直接订阅未读数变化 */ export function useUnreadCount(fetchUnreadCount: () => Promise): UseUnreadCountReturn { // 使用 Zustand selector 直接订阅未读数 const totalUnreadCount = useMessageStore(state => state.totalUnreadCount); const systemUnreadCount = useMessageStore(state => state.systemUnreadCount); useEffect(() => { // 初始获取 fetchUnreadCount(); }, []); const refresh = useCallback(async () => { await fetchUnreadCount(); }, [fetchUnreadCount]); return { totalUnreadCount, systemUnreadCount, refresh, }; } // ==================== useSystemUnreadCount - 获取系统消息未读数 ==================== interface UseSystemUnreadCountReturn { systemUnreadCount: number; setSystemUnreadCount: (count: number) => void; incrementSystemUnreadCount: () => void; decrementSystemUnreadCount: (count?: number) => void; } /** * 获取系统消息未读数 * 使用 Zustand selector 直接订阅 */ export function useSystemUnreadCount( setSystemUnreadCountService: (count: number) => void, incrementSystemUnreadCountService: () => void, decrementSystemUnreadCountService: (count?: number) => void ): UseSystemUnreadCountReturn { // 使用 Zustand selector 直接订阅 const systemUnreadCount = useMessageStore(state => state.systemUnreadCount); const setSystemUnreadCount = useCallback((count: number) => { setSystemUnreadCountService(count); }, [setSystemUnreadCountService]); const incrementSystemUnreadCount = useCallback(() => { incrementSystemUnreadCountService(); }, [incrementSystemUnreadCountService]); const decrementSystemUnreadCount = useCallback((count = 1) => { decrementSystemUnreadCountService(count); }, [decrementSystemUnreadCountService]); return { systemUnreadCount, setSystemUnreadCount, incrementSystemUnreadCount, decrementSystemUnreadCount, }; } // ==================== useConversation - 获取单个会话 ==================== interface UseConversationReturn { conversation: ConversationResponse | null; refresh: () => Promise; } /** * 获取单个会话 * 使用 Zustand selector 直接订阅会话变化 */ export function useConversation( conversationId: string, fetchConversationDetail: (conversationId: string) => Promise ): UseConversationReturn { // 使用 useShallow 避免每次返回新对象引用导致的无限循环 const conversation = useMessageStore( useShallow(state => state.conversations.get(conversationId) || null) ); const refresh = useCallback(async () => { await fetchConversationDetail(conversationId); }, [conversationId, fetchConversationDetail]); return { conversation, refresh, }; } // ==================== useMessageManager - 通用状态 ==================== interface UseMessageManagerReturn { isConnected: boolean; } /** * 通用消息管理状态 * 使用 Zustand selector 直接订阅连接状态 */ export function useMessageManager(initialize: () => Promise): UseMessageManagerReturn { // 使用 Zustand selector 直接订阅连接状态 const isConnected = useMessageStore(state => state.isWSConnected); useEffect(() => { initialize().catch(error => { console.error('[useMessageManager] 初始化失败:', error); }); }, []); return { isConnected, }; } // ==================== useCreateConversation - 创建会话 ==================== interface UseCreateConversationReturn { createConversation: (userId: string) => Promise; isCreating: boolean; } /** * 创建会话 */ export function useCreateConversation( createConversationService: (userId: string) => Promise ): UseCreateConversationReturn { const [isCreating, setIsCreating] = useState(false); const createConversation = useCallback(async (userId: string) => { setIsCreating(true); try { const conversation = await createConversationService(userId); return conversation; } catch (error) { console.error('[useCreateConversation] 创建会话失败:', error); return null; } finally { setIsCreating(false); } }, [createConversationService]); return { createConversation, isCreating, }; } // ==================== useUpdateConversation - 更新会话 ==================== interface UseUpdateConversationReturn { updateConversation: (conversationId: string, updates: Partial) => void; } /** * 更新会话 */ export function useUpdateConversation( updateConversationService: (conversationId: string, updates: Partial) => void ): UseUpdateConversationReturn { const updateConversation = useCallback((conversationId: string, updates: Partial) => { updateConversationService(conversationId, updates); }, [updateConversationService]); return { updateConversation, }; } // ==================== useGroupTyping - 群聊输入状态 ==================== interface UseGroupTypingReturn { typingUsers: string[]; } /** * 获取群聊输入状态 * 使用 Zustand selector 直接订阅 */ export function useGroupTyping(groupId: string): UseGroupTypingReturn { // 使用 useShallow 避免每次返回新数组引用导致的无限循环 const typingUsers = useMessageStore( useShallow(state => state.typingUsersMap.get(groupId) || []) ); return { typingUsers, }; } // ==================== useGroupMuted - 群聊禁言状态 ==================== interface UseGroupMutedReturn { isMuted: boolean; setMutedStatus: (muted: boolean) => void; } /** * 获取群聊禁言状态 * 使用 Zustand selector 直接订阅 */ export function useGroupMuted(groupId: string): UseGroupMutedReturn { // 使用 Zustand selector 直接订阅禁言状态 const isMuted = useMessageStore(state => state.mutedStatusMap.get(groupId) || false); const store = useMessageStore(); const setMutedStatus = useCallback((muted: boolean) => { store.setMutedStatus(groupId, muted); }, [groupId, store]); return { isMuted, setMutedStatus, }; } // ==================== useConnectionStatus - 连接状态 ==================== interface UseConnectionStatusReturn { isConnected: boolean; } /** * 获取连接状态 * 使用 Zustand selector 直接订阅 */ export function useConnectionStatus(): UseConnectionStatusReturn { const isConnected = useMessageStore(state => state.isWSConnected); return { isConnected, }; } // ==================== useIsInitialized - 初始化状态 ==================== interface UseIsInitializedReturn { isInitialized: boolean; } /** * 获取初始化状态 * 使用 Zustand selector 直接订阅 */ export function useIsInitialized(): UseIsInitializedReturn { const isInitialized = useMessageStore(state => state.isInitialized); return { isInitialized, }; }