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:
301
src/stores/message/MessageStateManager.ts
Normal file
301
src/stores/message/MessageStateManager.ts
Normal 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();
|
||||
Reference in New Issue
Block a user