refactor: 架构重构 - 解耦过度耦合模块

主要改动:
1. 创建乐观更新工具函数 (optimisticUpdate.ts)
   - 消除 userStore.ts 中的重复代码

2. 拆分 useResponsive.ts (485行 -> 12个专注模块)
   - useBreakpoint: 断点检测
   - useOrientation: 方向检测
   - usePlatform: 平台检测
   - useScreenSize: 屏幕尺寸
   - useResponsiveValue: 响应式值
   - useResponsiveStyle: 响应式样式
   - useMediaQuery: 媒体查询
   - useColumnCount: 列数计算
   - useResponsiveSpacing: 响应式间距

3. 整理数据层 (Repository 层)
   - ApiDataSource: API数据源
   - LocalDataSource: 本地数据源
   - CacheDataSource: 缓存数据源
   - MessageRepository: 消息仓库

4. 重构 messageManager.ts (2194行 -> 4个模块)
   - MessageStateManager: 状态管理
   - WebSocketMessageHandler: WebSocket处理
   - MessageSyncService: 消息同步
   - ReadReceiptManager: 已读管理

5. 导航解耦 (MainNavigator.tsx: 1118行 -> 100行)
   - 创建 NavigationService 解耦层
   - 拆分多个 Navigator 组件

架构改进:
- 单一职责原则: 每个模块职责明确
- 依赖倒置: 通过接口解耦
- 代码复用: 工具函数可被多处使用
- 可测试性: 各模块可独立测试
This commit is contained in:
lafay
2026-03-18 12:11:49 +08:00
parent 1ffbb63753
commit a6cdb97e24
70 changed files with 8175 additions and 1728 deletions

View File

@@ -0,0 +1,301 @@
/**
* 消息状态管理器
* 只负责管理状态,不包含业务逻辑
*/
import type { Message, Conversation, UnreadCount } from '../../core/entities/Message';
export type MessageEventType =
| 'conversations_updated'
| 'messages_updated'
| 'unread_count_updated'
| 'connection_changed'
| 'message_sent'
| 'message_read'
| 'message_received'
| 'message_recalled'
| 'typing_status'
| 'group_notice'
| 'error';
export interface MessageEvent {
type: MessageEventType;
payload: any;
timestamp: number;
}
export type MessageSubscriber = (event: MessageEvent) => void;
export interface MessageState {
conversations: Map<string, Conversation>;
conversationList: Conversation[];
messagesMap: Map<string, Message[]>;
unreadCount: UnreadCount;
isWebSocketConnected: boolean;
currentConversationId: string | null;
isLoading: boolean;
typingUsersMap: Map<string, string[]>;
mutedStatusMap: Map<string, boolean>;
}
export class MessageStateManager {
private state: MessageState;
private subscribers: Set<MessageSubscriber>;
private readStateVersion: number = 0;
private pendingReadMap: Map<string, { timestamp: number; version: number }> = new Map();
constructor() {
this.state = {
conversations: new Map(),
conversationList: [],
messagesMap: new Map(),
unreadCount: { total: 0, system: 0 },
isWebSocketConnected: false,
currentConversationId: null,
isLoading: false,
typingUsersMap: new Map(),
mutedStatusMap: new Map(),
};
this.subscribers = new Set();
}
// ==================== 状态获取 ====================
getState(): MessageState {
return this.state;
}
getConversations(): Conversation[] {
return this.state.conversationList;
}
getConversation(id: string): Conversation | undefined {
return this.state.conversations.get(id);
}
getMessages(conversationId: string): Message[] {
return this.state.messagesMap.get(conversationId) || [];
}
getUnreadCount(): UnreadCount {
return this.state.unreadCount;
}
getCurrentConversationId(): string | null {
return this.state.currentConversationId;
}
isConnected(): boolean {
return this.state.isWebSocketConnected;
}
getTypingUsers(groupId: string): string[] {
return this.state.typingUsersMap.get(groupId) || [];
}
getMutedStatus(groupId: string): boolean {
return this.state.mutedStatusMap.get(groupId) || false;
}
// ==================== 状态更新 ====================
setConversations(conversations: Conversation[]): void {
this.state.conversations.clear();
this.state.conversationList = conversations;
for (const conv of conversations) {
this.state.conversations.set(conv.id, conv);
}
this.updateUnreadCount();
this.notify({ type: 'conversations_updated', payload: conversations });
}
addConversation(conversation: Conversation): void {
this.state.conversations.set(conversation.id, conversation);
this.state.conversationList = Array.from(this.state.conversations.values());
this.updateUnreadCount();
this.notify({ type: 'conversations_updated', payload: this.state.conversationList });
}
updateConversation(id: string, updates: Partial<Conversation>): void {
const existing = this.state.conversations.get(id);
if (existing) {
const updated = { ...existing, ...updates };
this.state.conversations.set(id, updated);
this.state.conversationList = Array.from(this.state.conversations.values());
this.updateUnreadCount();
this.notify({ type: 'conversations_updated', payload: this.state.conversationList });
}
}
removeConversation(id: string): void {
this.state.conversations.delete(id);
this.state.messagesMap.delete(id);
this.state.conversationList = Array.from(this.state.conversations.values());
this.updateUnreadCount();
this.notify({ type: 'conversations_updated', payload: this.state.conversationList });
}
setMessages(conversationId: string, messages: Message[]): void {
this.state.messagesMap.set(conversationId, messages);
this.notify({ type: 'messages_updated', payload: { conversationId, messages } });
}
addMessage(conversationId: string, message: Message): void {
const messages = this.state.messagesMap.get(conversationId) || [];
const existingIndex = messages.findIndex(m => m.id === message.id);
if (existingIndex >= 0) {
messages[existingIndex] = message;
} else {
messages.push(message);
}
this.state.messagesMap.set(conversationId, messages);
this.notify({ type: 'messages_updated', payload: { conversationId, messages } });
}
prependMessages(conversationId: string, newMessages: Message[]): void {
const existing = this.state.messagesMap.get(conversationId) || [];
const merged = [...newMessages, ...existing];
const unique = this.deduplicateMessages(merged);
this.state.messagesMap.set(conversationId, unique);
this.notify({ type: 'messages_updated', payload: { conversationId, messages: unique } });
}
appendMessages(conversationId: string, newMessages: Message[]): void {
const existing = this.state.messagesMap.get(conversationId) || [];
const merged = [...existing, ...newMessages];
const unique = this.deduplicateMessages(merged);
this.state.messagesMap.set(conversationId, unique);
this.notify({ type: 'messages_updated', payload: { conversationId, messages: unique } });
}
updateMessage(conversationId: string, messageId: string, updates: Partial<Message>): void {
const messages = this.state.messagesMap.get(conversationId);
if (!messages) return;
const index = messages.findIndex(m => m.id === messageId);
if (index >= 0) {
messages[index] = { ...messages[index], ...updates };
this.notify({ type: 'messages_updated', payload: { conversationId, messages } });
}
}
setUnreadCount(count: UnreadCount): void {
this.state.unreadCount = count;
this.notify({ type: 'unread_count_updated', payload: count });
}
setWebSocketConnected(connected: boolean): void {
this.state.isWebSocketConnected = connected;
this.notify({ type: 'connection_changed', payload: connected });
}
setCurrentConversation(id: string | null): void {
this.state.currentConversationId = id;
}
setLoading(loading: boolean): void {
this.state.isLoading = loading;
}
setTypingUsers(groupId: string, userIds: string[]): void {
this.state.typingUsersMap.set(groupId, userIds);
this.notify({ type: 'typing_status', payload: { groupId, userIds } });
}
setMutedStatus(groupId: string, muted: boolean): void {
this.state.mutedStatusMap.set(groupId, muted);
}
// ==================== 已读状态保护 ====================
beginReadOperation(conversationId: string): number {
this.readStateVersion++;
this.pendingReadMap.set(conversationId, {
timestamp: Date.now(),
version: this.readStateVersion,
});
return this.readStateVersion;
}
endReadOperation(conversationId: string): void {
setTimeout(() => {
this.pendingReadMap.delete(conversationId);
}, 5000);
}
isReadOperationPending(conversationId: string): boolean {
const record = this.pendingReadMap.get(conversationId);
if (!record) return false;
return Date.now() - record.timestamp < 5000;
}
getReadStateVersion(): number {
return this.readStateVersion;
}
// ==================== 订阅机制 ====================
subscribe(callback: MessageSubscriber): () => void {
this.subscribers.add(callback);
return () => this.subscribers.delete(callback);
}
private notify(event: Omit<MessageEvent, 'timestamp'>): void {
const fullEvent: MessageEvent = {
...event,
timestamp: Date.now(),
};
this.subscribers.forEach(callback => {
try {
callback(fullEvent);
} catch (error) {
console.error('[MessageStateManager] Subscriber error:', error);
}
});
}
// ==================== 工具方法 ====================
private updateUnreadCount(): void {
let total = 0;
let system = 0;
for (const conv of this.state.conversations.values()) {
total += conv.unreadCount || 0;
}
this.state.unreadCount = { total, system };
this.notify({ type: 'unread_count_updated', payload: this.state.unreadCount });
}
private deduplicateMessages(messages: Message[]): Message[] {
const seen = new Set<string>();
return messages.filter(msg => {
if (seen.has(msg.id)) return false;
seen.add(msg.id);
return true;
});
}
reset(): void {
this.state = {
conversations: new Map(),
conversationList: [],
messagesMap: new Map(),
unreadCount: { total: 0, system: 0 },
isWebSocketConnected: false,
currentConversationId: null,
isLoading: false,
typingUsersMap: new Map(),
mutedStatusMap: new Map(),
};
this.pendingReadMap.clear();
this.readStateVersion = 0;
}
}
export const messageStateManager = new MessageStateManager();