refactor(message): improve message synchronization and hook reliability
Some checks failed
Frontend CI / ota-android (push) Successful in 1m29s
Frontend CI / build-and-push-web (push) Failing after 1m31s
Frontend CI / ota-ios (push) Successful in 4m4s
Frontend CI / build-android-apk (push) Successful in 29m5s

Refactor the message management system to address synchronization issues and improve performance through request deduplication and enhanced lifecycle management.

- Implement in-flight promise tracking in `MessageSyncService` to prevent redundant network requests for conversations and messages.
- Enhance `useMessages` hook with `useFocusEffect` to trigger automatic synchronization when a chat screen regains focus.
- Add `forceSync` capability to `MessageManager` and `MessageSyncService` to allow manual overrides of loading states during re-entry.
- Consolidate WebSocket synchronization logic in `WSMessageHandler` using a throttled and deduplicated trigger mechanism.
- Clean up unused styles and deprecated hooks (`baseHooks.ts`, `bubbleStyles.ts`, `inputStyles.ts`).
- Update `metro.config.js` and `package.json` to include `@expo/ui` and optimize resolver settings.
This commit is contained in:
2026-06-03 10:31:46 +08:00
parent 2e6912dddf
commit 2e2f6e3467
13 changed files with 396 additions and 1115 deletions

View File

@@ -12,6 +12,7 @@
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import { useFocusEffect } from 'expo-router';
import { useShallow } from 'zustand/react/shallow';
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
import { messageManager } from './MessageManager';
@@ -96,10 +97,15 @@ interface UseMessagesReturn {
/**
* 获取指定会话的消息
* 使用 Zustand selector 自动订阅消息变化
*
* 核心修复:
* 1. useFocusEffect屏幕重新获得焦点时强制同步消息解决"需要退出重进才能看到新消息"的问题
* 2. 清理 isLoadingMessages组件卸载时清除残留的 loading 锁,防止下次进入被阻断
* 3. 重入时 forceSync相同 conversationId 重复进入时强制拉取最新消息
*/
export function useMessages(conversationId: string | null): UseMessagesReturn {
const normalizedConversationId = conversationId ? String(conversationId) : null;
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
const messages = useMessageStore(
useShallow(state => {
@@ -107,30 +113,68 @@ export function useMessages(conversationId: string | null): UseMessagesReturn {
return state.messagesMap.get(normalizedConversationId) ?? EMPTY_MESSAGES;
})
);
const isLoadingMessages = useMessageStore(state =>
const isLoadingMessages = useMessageStore(state =>
normalizedConversationId ? state.loadingMessagesSet.has(normalizedConversationId) : false
);
const [hasMore, setHasMore] = useState(true);
const loadMoreInFlightRef = useRef(false);
// 追踪上次激活的 conversationId避免同一会话重复激活时不必要的 forceSync
const lastActivatedIdRef = useRef<string | null>(null);
// 追踪 useEffect 激活时间,与 useFocusEffect 去重
const lastActivatedAtRef = useRef<number>(0);
useEffect(() => {
if (!normalizedConversationId) {
return;
}
lastActivatedAtRef.current = Date.now();
const isReentry = lastActivatedIdRef.current === normalizedConversationId;
// 架构入口:激活会话并完成初始化/同步
messageManager.activateConversation(normalizedConversationId).catch(error => {
// 重入同一会话时 forceSync确保拿到最新消息
messageManager.activateConversation(normalizedConversationId, { forceSync: isReentry }).catch(error => {
console.error('[useMessages] 激活会话失败:', error);
});
lastActivatedIdRef.current = normalizedConversationId;
return () => {
// 清理活动会话
// 清理活动会话 + 清除残留的 loading 锁
if (messageManager.getActiveConversation() === normalizedConversationId) {
messageManager.setActiveConversation(null);
}
useMessageStore.getState().setLoadingMessages(normalizedConversationId, false);
};
}, [normalizedConversationId]);
// 屏幕重新获得焦点时,同步活动会话的最新消息
useFocusEffect(
useCallback(() => {
if (!normalizedConversationId) return;
// 如果刚被 useEffect 激活500ms 内),跳过焦点同步,避免重复
if (Date.now() - lastActivatedAtRef.current < 500) return;
// 先清除可能残留的 loading 锁,防止上次退出时未正常清理
useMessageStore.getState().setLoadingMessages(normalizedConversationId, false);
// 增量同步最新消息(从当前内存最高 seq 开始拉取)
const currentMessages = useMessageStore.getState().getMessages(normalizedConversationId);
const maxSeq = currentMessages.reduce((max, m) => Math.max(max, m.seq || 0), 0);
if (maxSeq > 0) {
messageManager.fetchMessages(normalizedConversationId, maxSeq).catch(error => {
console.error('[useMessages] 焦点同步消息失败:', error);
});
} else {
messageManager.fetchMessages(normalizedConversationId).catch(error => {
console.error('[useMessages] 焦点同步消息(冷启动)失败:', error);
});
}
}, [normalizedConversationId])
);
const loadMore = useCallback(async () => {
if (!conversationId || !hasMore || loadMoreInFlightRef.current) return;