Files
frontend/src/stores/message/hooks.ts

483 lines
13 KiB
TypeScript
Raw Normal View History

/**
* 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<void>;
loadMore: () => Promise<void>;
hasMore: boolean;
}
/**
*
* MessageListScreen
* 使 Zustand selector
*/
export function useConversations(
fetchConversations: (forceRefresh: boolean, source: string) => Promise<void>,
loadMoreConversations: () => Promise<void>,
canLoadMore: () => boolean,
initialize: () => Promise<void>
): 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<void>;
refresh: () => Promise<void>;
}
/**
*
* 使 Zustand selector
*/
export function useMessages(
conversationId: string,
fetchMessages: (conversationId: string) => Promise<void>,
loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise<MessageResponse[]>
): 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<MessageResponse | null>;
isSending: boolean;
}
/**
*
*/
export function useSendMessage(
conversationId: string,
sendMessageService: (conversationId: string, segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>
): 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<void>;
markAllAsRead: () => Promise<void>;
}
/**
*
*/
export function useMarkAsRead(
conversationId: string,
markAsReadService: (conversationId: string, seq: number) => Promise<void>,
markAllAsReadService: () => Promise<void>
): 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<void>;
}
/**
*
* 使 Zustand selector
*/
export function useUnreadCount(fetchUnreadCount: () => Promise<void>): 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<void>;
}
/**
*
* 使 Zustand selector
*/
export function useConversation(
conversationId: string,
fetchConversationDetail: (conversationId: string) => Promise<ConversationResponse | null>
): 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<void>): 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<ConversationResponse | null>;
isCreating: boolean;
}
/**
*
*/
export function useCreateConversation(
createConversationService: (userId: string) => Promise<ConversationResponse | null>
): 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<ConversationResponse>) => void;
}
/**
*
*/
export function useUpdateConversation(
updateConversationService: (conversationId: string, updates: Partial<ConversationResponse>) => void
): UseUpdateConversationReturn {
const updateConversation = useCallback((conversationId: string, updates: Partial<ConversationResponse>) => {
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,
};
}