feat(message): enhance message store management and UI stability
Implement `removeMessage` in `useMessageStore` to allow for atomic message deletion from the state. Improve `useChatScreen` loading logic to prevent layout jumps and ensure consistent UI state during initial message loading. Additionally, add type safety for WebSocket sequence numbers and optimize message selector stability. - Add `removeMessage` action to `useMessageStore` for state-consistent deletions - Refine `useChatScreen` loading state logic to prevent FlashList layout issues - Ensure `useMessages` hook returns a stable `EMPTY_MESSAGES` constant - Cast WebSocket `seq` to integer to ensure consistent numeric comparison - Update `useChatScreen` to use `useMessageStore` for immediate local deletion feedback
This commit is contained in:
@@ -26,6 +26,7 @@ import { uploadService } from '@/services/upload';
|
||||
import { ApiError } from '@/services/core';
|
||||
// 【新架构】使用 MessageManager
|
||||
import { useChat, useGroupTyping, useGroupMuted, messageManager, callStore } from '../../../../stores';
|
||||
import { useMessageStore } from '../../../../stores/message';
|
||||
import { groupService } from '@/services/message';
|
||||
import { userManager } from '../../../../stores/user';
|
||||
import { groupManager } from '../../../../stores/group';
|
||||
@@ -295,17 +296,14 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
}, [followRestricted, myPrivateSentCount]);
|
||||
|
||||
// 加载态语义修正:
|
||||
// isLoadingMessages 在分页加载历史时也会短暂为 true。
|
||||
// 若直接驱动 ChatScreen 的 loading,会导致消息列表被卸载重挂载,触发“回到底部”。
|
||||
// 这里只在“首屏且尚未展示过消息列表”时展示 loading 占位。
|
||||
// 首屏加载:尚未展示过消息列表 且 无消息 → 显示 loading(避免 FlashList 以空 data 挂载后 inverted 布局异常)
|
||||
// 一旦有消息或加载完成(isLoadingMessages 归 false),即解除 loading
|
||||
useEffect(() => {
|
||||
if (messageManagerMessages.length > 0) {
|
||||
hasShownMessageListRef.current = true;
|
||||
}
|
||||
const shouldShowInitialLoading =
|
||||
!hasShownMessageListRef.current &&
|
||||
isLoadingMessages &&
|
||||
messageManagerMessages.length === 0;
|
||||
!hasShownMessageListRef.current && messageManagerMessages.length === 0 && isLoadingMessages;
|
||||
setLoading(shouldShowInitialLoading);
|
||||
}, [isLoadingMessages, messageManagerMessages.length]);
|
||||
|
||||
@@ -1400,16 +1398,15 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
const handleDeleteMessage = useCallback(async (messageId: string) => {
|
||||
try {
|
||||
await messageService.deleteMessage(messageId);
|
||||
// 从本地状态移除(MessageManager 不会自动处理删除)
|
||||
const updatedMessages = messages.filter(m => m.id !== messageId);
|
||||
// 注意:这里我们依赖 MessageManager 的数据,所以需要通过其他方式刷新
|
||||
// 暂时只删除本地数据库
|
||||
await messageRepository.delete(messageId);
|
||||
if (conversationId) {
|
||||
useMessageStore.getState().removeMessage(conversationId, messageId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除消息失败:', error);
|
||||
Alert.alert('删除失败', '无法删除消息');
|
||||
}
|
||||
}, [messages]);
|
||||
}, [conversationId]);
|
||||
|
||||
// 清空会话的所有聊天记录
|
||||
const handleClearConversation = useCallback(async () => {
|
||||
|
||||
@@ -18,6 +18,8 @@ import { messageManager } from './MessageManager';
|
||||
import { useMessageStore } from './store';
|
||||
import { useUnreadCountQuery } from '../../hooks/useUnreadCount';
|
||||
|
||||
const EMPTY_MESSAGES: MessageResponse[] = [];
|
||||
|
||||
// ==================== useConversations - 获取会话列表 ====================
|
||||
|
||||
interface UseConversationsReturn {
|
||||
@@ -100,9 +102,10 @@ export function useMessages(conversationId: string | null): UseMessagesReturn {
|
||||
|
||||
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||
const messages = useMessageStore(
|
||||
useShallow(state =>
|
||||
normalizedConversationId ? (state.messagesMap.get(normalizedConversationId) || []) : []
|
||||
)
|
||||
useShallow(state => {
|
||||
if (!normalizedConversationId) return EMPTY_MESSAGES;
|
||||
return state.messagesMap.get(normalizedConversationId) ?? EMPTY_MESSAGES;
|
||||
})
|
||||
);
|
||||
const isLoadingMessages = useMessageStore(state =>
|
||||
normalizedConversationId ? state.loadingMessagesSet.has(normalizedConversationId) : false
|
||||
|
||||
@@ -340,7 +340,7 @@ export class WSMessageHandler implements IWSMessageHandler {
|
||||
id,
|
||||
conversation_id: normalizedConversationId,
|
||||
sender_id,
|
||||
seq,
|
||||
seq: typeof seq === 'string' ? parseInt(seq, 10) : (seq ?? 0),
|
||||
segments: segments || [],
|
||||
created_at,
|
||||
status: 'normal',
|
||||
|
||||
@@ -85,6 +85,7 @@ export interface MessageActions {
|
||||
removeConversation: (conversationId: string) => void;
|
||||
setMessages: (conversationId: string, messages: MessageResponse[]) => void;
|
||||
mergeMessages: (conversationId: string, incoming: MessageResponse[]) => void;
|
||||
removeMessage: (conversationId: string, messageId: string) => void;
|
||||
patchMessages: (conversationId: string, patches: Map<string, Partial<MessageResponse>>) => void;
|
||||
setUnreadCount: (total: number, system: number) => void;
|
||||
setLastSystemMessageAt: (time: string | null) => void;
|
||||
@@ -349,6 +350,19 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
removeMessage: (conversationId: string, messageId: string) => {
|
||||
const normalizedId = normalizeConversationId(conversationId);
|
||||
set(state => {
|
||||
const existing = state.messagesMap.get(normalizedId);
|
||||
if (!existing) return state;
|
||||
const filtered = existing.filter(m => String(m.id) !== String(messageId));
|
||||
if (filtered.length === existing.length) return state;
|
||||
const newMessagesMap = new Map(state.messagesMap);
|
||||
newMessagesMap.set(normalizedId, filtered);
|
||||
return { messagesMap: newMessagesMap };
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 原子性 patch:按消息 ID 更新指定字段(如 sender、status)
|
||||
* 不影响其他消息,不丢失并发写入
|
||||
|
||||
Reference in New Issue
Block a user