/** * 消息模块 React Hooks * * 提供React组件与消息状态管理的集成 * 所有hooks都基于zustand store和订阅机制 * 确保组件能实时获取状态更新 * * 重构说明:直接使用 zustand store,移除了对 messageManager 的直接依赖 */ import { useState, useEffect, useCallback, useRef } from 'react'; import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto'; import { useMessageStore, subscriptionManager, normalizeConversationId } from './store'; import type { MessageEvent, MessageSubscriber } from './types'; // ==================== useConversations - 获取会话列表 ==================== interface UseConversationsReturn { conversations: ConversationResponse[]; isLoading: boolean; refresh: () => Promise; loadMore: () => Promise; hasMore: boolean; } /** * 获取会话列表 * 用于 MessageListScreen 等需要显示会话列表的组件 */ export function useConversations( fetchConversations: (forceRefresh: boolean, source: string) => Promise, loadMoreConversations: () => Promise, canLoadMore: () => boolean, initialize: () => Promise ): UseConversationsReturn { const store = useMessageStore(); const [conversations, setConversations] = useState(() => store.getConversations()); const [isLoading, setIsLoading] = useState(() => store.isLoading()); const [hasMore, setHasMore] = useState(() => canLoadMore()); useEffect(() => { // 订阅事件 const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { switch (event.type) { case 'conversations_updated': setConversations(store.getConversations()); setHasMore(canLoadMore()); break; case 'conversations_loading': setIsLoading(!!event.payload?.loading); break; case 'connection_changed': // 连接状态变化时可能需要刷新 break; } }); // 初始化 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); unsubscribe(); }; }, []); 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; } /** * 获取指定会话的消息 */ export function useMessages( conversationId: string, fetchMessages: (conversationId: string) => Promise, loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise ): UseMessagesReturn { const store = useMessageStore(); const normalizedConversationId = normalizeConversationId(conversationId); const [messages, setMessages] = useState(() => store.getMessages(normalizedConversationId) ); const [isLoading, setIsLoading] = useState(() => store.isLoadingMessages(normalizedConversationId) ); const [hasMore, setHasMore] = useState(true); useEffect(() => { const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { if (event.type === 'messages_updated' && event.payload.conversationId === normalizedConversationId) { setMessages(event.payload.messages); } }); // 激活会话 fetchMessages(normalizedConversationId).catch(error => { console.error('[useMessages] 激活会话失败:', error); }); return () => { // 清理活动会话 if (store.getActiveConversation() === normalizedConversationId) { store.setCurrentConversation(null); } unsubscribe(); }; }, [normalizedConversationId]); const loadMore = useCallback(async () => { const currentMessages = store.getMessages(conversationId); 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, loadMoreMessages]); const refresh = useCallback(async () => { await fetchMessages(conversationId); setMessages(store.getMessages(conversationId)); }, [conversationId, fetchMessages]); return { messages, isLoading, 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; } /** * 获取未读数 */ export function useUnreadCount(fetchUnreadCount: () => Promise): UseUnreadCountReturn { const store = useMessageStore(); const [counts, setCounts] = useState(() => store.getUnreadCount()); useEffect(() => { const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { if (event.type === 'unread_count_updated') { setCounts(store.getUnreadCount()); } }); // 初始获取 fetchUnreadCount(); return unsubscribe; }, []); const refresh = useCallback(async () => { await fetchUnreadCount(); }, [fetchUnreadCount]); return { totalUnreadCount: counts.total, systemUnreadCount: counts.system, refresh, }; } // ==================== useSystemUnreadCount - 获取系统消息未读数 ==================== interface UseSystemUnreadCountReturn { systemUnreadCount: number; setSystemUnreadCount: (count: number) => void; incrementSystemUnreadCount: () => void; decrementSystemUnreadCount: (count?: number) => void; } /** * 获取系统消息未读数 */ export function useSystemUnreadCount( setSystemUnreadCountService: (count: number) => void, incrementSystemUnreadCountService: () => void, decrementSystemUnreadCountService: (count?: number) => void ): UseSystemUnreadCountReturn { const store = useMessageStore(); const [systemUnreadCount, setSystemUnreadCountState] = useState(() => store.getUnreadCount().system ); useEffect(() => { const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { if (event.type === 'unread_count_updated') { setSystemUnreadCountState(store.getUnreadCount().system); } }); return unsubscribe; }, []); 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; } /** * 获取单个会话 */ export function useConversation( conversationId: string, fetchConversationDetail: (conversationId: string) => Promise ): UseConversationReturn { const store = useMessageStore(); const [conversation, setConversation] = useState(() => store.getConversation(conversationId) ); useEffect(() => { setConversation(store.getConversation(conversationId)); const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { if (event.type === 'conversations_updated') { const updated = store.getConversation(conversationId); setConversation(updated); } }); return unsubscribe; }, [conversationId]); const refresh = useCallback(async () => { await fetchConversationDetail(conversationId); setConversation(store.getConversation(conversationId)); }, [conversationId, fetchConversationDetail]); return { conversation, refresh, }; } // ==================== useMessageManager - 通用订阅 ==================== interface UseMessageManagerReturn { isConnected: boolean; subscribe: (subscriber: MessageSubscriber) => () => void; } /** * 通用消息订阅 */ export function useMessageManager(initialize: () => Promise): UseMessageManagerReturn { const store = useMessageStore(); const [isConnected, setIsConnected] = useState(() => store.isConnected()); useEffect(() => { const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { if (event.type === 'connection_changed') { setIsConnected(event.payload.connected); } }); initialize().then(() => { setIsConnected(store.isConnected()); }).catch(error => { console.error('[useMessageManager] 初始化失败:', error); }); return unsubscribe; }, []); const subscribe = useCallback((subscriber: MessageSubscriber) => { return subscriptionManager.subscribe(subscriber); }, []); return { isConnected, subscribe, }; } // ==================== 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[]; } /** * 获取群聊输入状态 */ export function useGroupTyping(groupId: string): UseGroupTypingReturn { const store = useMessageStore(); const [typingUsers, setTypingUsers] = useState(() => store.getTypingUsers(groupId) ); useEffect(() => { setTypingUsers(store.getTypingUsers(groupId)); const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { if (event.type === 'typing_status' && event.payload.groupId === groupId) { setTypingUsers(event.payload.typingUsers); } }); return unsubscribe; }, [groupId]); return { typingUsers, }; } // ==================== useGroupMuted - 群聊禁言状态 ==================== interface UseGroupMutedReturn { isMuted: boolean; setMutedStatus: (muted: boolean) => void; } /** * 获取群聊禁言状态 */ export function useGroupMuted(groupId: string): UseGroupMutedReturn { const store = useMessageStore(); const [isMuted, setIsMuted] = useState(() => store.isMuted(groupId)); useEffect(() => { setIsMuted(store.isMuted(groupId)); const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => { if (event.type === 'group_notice' && event.payload.groupId === groupId) { if (event.payload.noticeType === 'muted' || event.payload.noticeType === 'unmuted') { setIsMuted(event.payload.noticeType === 'muted'); } } }); return unsubscribe; }, [groupId]); const setMutedStatus = useCallback((muted: boolean) => { store.setMutedStatus(groupId, muted); }, [groupId]); return { isMuted, setMutedStatus, }; } // ==================== useActiveConversation - 活动会话 ==================== interface UseActiveConversationReturn { activeConversationId: string | null; setActiveConversation: (conversationId: string | null) => void; } /** * 获取/设置活动会话 */ export function useActiveConversation(): UseActiveConversationReturn { const store = useMessageStore(); const [activeConversationId, setActiveConversationId] = useState(() => store.getActiveConversation() ); const setActiveConversation = useCallback((conversationId: string | null) => { store.setCurrentConversation(conversationId); setActiveConversationId(conversationId); }, []); return { activeConversationId, setActiveConversation, }; }