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,287 @@
/**
* 乐观更新工具函数测试
*/
import { optimisticUpdate, simpleOptimisticUpdate, createOptimisticUpdaterFactory } from '../optimisticUpdate';
// 模拟数据
interface TestState {
posts: Array<{ id: string; likes: number; isLiked: boolean }>;
users: Array<{ id: string; isFollowing: boolean }>;
}
const initialState: TestState = {
posts: [
{ id: '1', likes: 10, isLiked: false },
{ id: '2', likes: 5, isLiked: true },
],
users: [
{ id: 'user1', isFollowing: false },
{ id: 'user2', isFollowing: true },
],
};
describe('optimisticUpdate', () => {
let currentState: TestState;
beforeEach(() => {
currentState = JSON.parse(JSON.stringify(initialState));
});
describe('成功场景', () => {
it('应该执行乐观更新并在API成功后保持更新', async () => {
const mockApiCall = jest.fn().mockResolvedValue({ success: true, likes: 11 });
const result = await optimisticUpdate<TestState, { success: boolean; likes: number }>({
getState: () => currentState,
optimisticUpdate: (state) => ({
...state,
posts: state.posts.map((p) =>
p.id === '1' ? { ...p, likes: p.likes + 1, isLiked: true } : p
),
}),
apiCall: mockApiCall,
onSuccess: (result, current) => ({
...current,
posts: current.posts.map((p) =>
p.id === '1' ? { ...p, likes: result.likes } : p
),
}),
});
// 验证API被调用
expect(mockApiCall).toHaveBeenCalledTimes(1);
// 验证返回结果
expect(result).toEqual({ success: true, likes: 11 });
});
it('应该在乐观更新后立即改变状态', async () => {
const mockApiCall = jest.fn().mockImplementation(() => {
// 在API调用期间验证状态已被乐观更新
expect(currentState.posts[0].isLiked).toBe(true);
expect(currentState.posts[0].likes).toBe(11);
return Promise.resolve({ success: true });
});
await optimisticUpdate<TestState, any>({
getState: () => currentState,
optimisticUpdate: (state) => {
const newState = {
...state,
posts: state.posts.map((p) =>
p.id === '1' ? { ...p, likes: p.likes + 1, isLiked: true } : p
),
};
currentState = newState; // 模拟状态更新
return newState;
},
apiCall: mockApiCall,
});
});
});
describe('失败回滚场景', () => {
it('应该在API失败时回滚到原始状态', async () => {
const mockApiCall = jest.fn().mockRejectedValue(new Error('Network error'));
const onError = jest.fn((error, original) => original);
const result = await optimisticUpdate<TestState, any>({
getState: () => currentState,
optimisticUpdate: (state) => ({
...state,
posts: state.posts.map((p) =>
p.id === '1' ? { ...p, likes: p.likes + 1, isLiked: true } : p
),
}),
apiCall: mockApiCall,
onError,
errorMessage: '点赞失败',
});
// 验证API被调用
expect(mockApiCall).toHaveBeenCalledTimes(1);
// 验证onError被调用
expect(onError).toHaveBeenCalledTimes(1);
expect(onError).toHaveBeenCalledWith(
expect.any(Error),
initialState
);
// 验证返回undefined因为失败了
expect(result).toBeUndefined();
});
it('应该在静默模式下不抛出错误', async () => {
const mockApiCall = jest.fn().mockRejectedValue(new Error('Network error'));
// 不应该抛出错误
await expect(
optimisticUpdate<TestState, any>({
getState: () => currentState,
optimisticUpdate: (state) => state,
apiCall: mockApiCall,
silent: true,
})
).resolves.toBeUndefined();
});
it('应该在非静默模式下抛出错误', async () => {
const mockApiCall = jest.fn().mockRejectedValue(new Error('Network error'));
// 应该抛出错误
await expect(
optimisticUpdate<TestState, any>({
getState: () => currentState,
optimisticUpdate: (state) => state,
apiCall: mockApiCall,
silent: false,
})
).rejects.toThrow('Network error');
});
});
describe('错误处理', () => {
it('应该记录错误日志', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const mockApiCall = jest.fn().mockRejectedValue(new Error('API Error'));
await optimisticUpdate<TestState, any>({
getState: () => currentState,
optimisticUpdate: (state) => state,
apiCall: mockApiCall,
errorMessage: '自定义错误消息',
silent: true,
});
expect(consoleSpy).toHaveBeenCalledWith('自定义错误消息:', expect.any(Error));
consoleSpy.mockRestore();
});
});
});
describe('simpleOptimisticUpdate', () => {
let currentState: TestState;
let setStateMock: jest.Mock;
beforeEach(() => {
currentState = JSON.parse(JSON.stringify(initialState));
setStateMock = jest.fn((newState) => {
currentState = newState;
});
});
it('应该成功执行乐观更新', async () => {
const mockApiCall = jest.fn().mockResolvedValue(undefined);
await simpleOptimisticUpdate<TestState>({
getState: () => currentState,
setState: setStateMock,
optimisticUpdate: (state) => ({
...state,
posts: state.posts.map((p) =>
p.id === '1' ? { ...p, likes: p.likes + 1 } : p
),
}),
rollbackState: (original) => original,
apiCall: mockApiCall,
});
expect(mockApiCall).toHaveBeenCalledTimes(1);
expect(setStateMock).toHaveBeenCalledTimes(1);
});
it('应该在失败时回滚状态', async () => {
const mockApiCall = jest.fn().mockRejectedValue(new Error('Error'));
const rollbackMock = jest.fn((original) => original);
await simpleOptimisticUpdate<TestState>({
getState: () => currentState,
setState: setStateMock,
optimisticUpdate: (state) => ({
...state,
posts: state.posts.map((p) =>
p.id === '1' ? { ...p, likes: p.likes + 1 } : p
),
}),
rollbackState: rollbackMock,
apiCall: mockApiCall,
silent: true,
});
// 验证乐观更新和回滚都被调用
expect(setStateMock).toHaveBeenCalledTimes(2);
expect(rollbackMock).toHaveBeenCalledWith(initialState);
});
});
describe('createOptimisticUpdaterFactory', () => {
interface StoreState {
posts: Array<{ id: string; likes: number }>;
count: number;
}
let storeState: StoreState;
let setMock: jest.Mock;
let getMock: jest.Mock;
beforeEach(() => {
storeState = {
posts: [{ id: '1', likes: 10 }],
count: 0,
};
setMock = jest.fn((fn) => {
storeState = fn(storeState);
});
getMock = jest.fn(() => storeState);
});
it('应该创建乐观更新器并正常工作', async () => {
const optimisticUpdater = createOptimisticUpdaterFactory<StoreState>(setMock, getMock);
const mockApiCall = jest.fn().mockResolvedValue(undefined);
await optimisticUpdater({
key: 'posts',
optimisticUpdate: (posts) =>
posts.map((p) => (p.id === '1' ? { ...p, likes: p.likes + 1 } : p)),
rollbackState: (original) => original,
apiCall: mockApiCall,
});
expect(mockApiCall).toHaveBeenCalledTimes(1);
expect(setMock).toHaveBeenCalledTimes(1);
expect(storeState.posts[0].likes).toBe(11);
});
it('应该在API失败时回滚状态', async () => {
const optimisticUpdater = createOptimisticUpdaterFactory<StoreState>(setMock, getMock);
const mockApiCall = jest.fn().mockRejectedValue(new Error('Error'));
await optimisticUpdater({
key: 'posts',
optimisticUpdate: (posts) =>
posts.map((p) => (p.id === '1' ? { ...p, likes: p.likes + 1 } : p)),
rollbackState: (original) => original,
apiCall: mockApiCall,
silent: true,
});
// 乐观更新 + 回滚 = 2次set调用
expect(setMock).toHaveBeenCalledTimes(2);
// 最终状态应该和初始状态一样
expect(storeState.posts[0].likes).toBe(10);
});
it('应该处理不同的state key', async () => {
const optimisticUpdater = createOptimisticUpdaterFactory<StoreState>(setMock, getMock);
const mockApiCall = jest.fn().mockResolvedValue(undefined);
await optimisticUpdater({
key: 'count',
optimisticUpdate: (count) => count + 1,
rollbackState: (original) => original,
apiCall: mockApiCall,
});
expect(storeState.count).toBe(1);
});
});

View File

@@ -0,0 +1,201 @@
/**
* 乐观更新工具函数
* 用于统一管理乐观更新逻辑,减少重复代码
*/
export interface OptimisticUpdateOptions<T, R> {
/** 获取当前状态 */
getState: () => T;
/** 执行乐观更新 */
optimisticUpdate: (current: T) => T;
/** API调用函数 */
apiCall: () => Promise<R>;
/** API成功时的回调返回新的状态 */
onSuccess?: (result: R, current: T) => T;
/** API失败时的回滚回调 */
onError?: (error: any, original: T) => T;
/** 错误消息 */
errorMessage?: string;
/** 是否静默处理错误(不抛出) */
silent?: boolean;
}
/**
* 执行乐观更新
*
* 流程:
* 1. 保存原始状态
* 2. 执行乐观更新
* 3. 调用API
* 4. 成功时使用服务器数据更新状态
* 5. 失败时回滚到原始状态
*
* @template T 状态类型
* @template R API返回类型
* @param options 配置选项
* @returns Promise<R> API返回结果
*/
export async function optimisticUpdate<T, R>(
options: OptimisticUpdateOptions<T, R>
): Promise<R | undefined> {
const {
getState,
optimisticUpdate,
apiCall,
onSuccess,
onError,
errorMessage = '操作失败',
silent = true,
} = options;
// 保存原始状态
const originalState = getState();
// 执行乐观更新
const optimisticState = optimisticUpdate(originalState);
try {
// 调用API
const result = await apiCall();
// API成功使用服务器数据更新状态如果提供了onSuccess
if (onSuccess) {
onSuccess(result, optimisticState);
}
return result;
} catch (error) {
console.error(`${errorMessage}:`, error);
// API失败回滚状态
if (onError) {
onError(error, originalState);
}
// 如果不是静默模式,抛出错误
if (!silent) {
throw error;
}
return undefined;
}
}
/**
* 简化版的乐观更新(适用于不需要服务器返回数据的场景)
*
* @template T 状态类型
* @param options 配置选项
*/
export async function simpleOptimisticUpdate<T>(
options: Omit<OptimisticUpdateOptions<T, void>, 'onSuccess' | 'onError'> & {
setState: (state: T) => void;
rollbackState: (original: T) => T;
}
): Promise<void> {
const {
getState,
setState,
optimisticUpdate,
rollbackState,
apiCall,
errorMessage = '操作失败',
silent = true,
} = options;
// 保存原始状态
const originalState = getState();
// 执行乐观更新
setState(optimisticUpdate(originalState));
try {
// 调用API
await apiCall();
} catch (error) {
console.error(`${errorMessage}:`, error);
// API失败回滚状态
setState(rollbackState(originalState));
// 如果不是静默模式,抛出错误
if (!silent) {
throw error;
}
}
}
/**
* 创建乐观更新器用于Zustand store
*
* 使用方式:
* ```typescript
* const createOptimisticUpdater = createOptimisticUpdaterFactory(set, get);
*
* const likePost = async (postId: string) => {
* await createOptimisticUpdater({
* getCurrentState: () => get().posts,
* setState: (posts) => set({ posts }),
* optimisticUpdate: (posts) => posts.map(...),
* rollbackState: (originalPosts) => originalPosts,
* apiCall: () => postService.likePost(postId),
* errorMessage: '点赞失败',
* });
* };
* ```
*/
export function createOptimisticUpdaterFactory<T extends Record<string, any>>(
set: (fn: (state: T) => T) => void,
get: () => T
) {
return function optimisticUpdater<K extends keyof T>(
options: {
key: K;
optimisticUpdate: (current: T[K]) => T[K];
rollbackState: (original: T[K]) => T[K];
apiCall: () => Promise<any>;
errorMessage?: string;
silent?: boolean;
}
): Promise<void> {
const {
key,
optimisticUpdate,
rollbackState,
apiCall,
errorMessage = '操作失败',
silent = true,
} = options;
// 保存原始状态
const originalState = get()[key];
// 执行乐观更新
set((state) => ({
...state,
[key]: optimisticUpdate(state[key]),
}));
return new Promise((resolve, reject) => {
apiCall()
.then(() => {
resolve();
})
.catch((error) => {
console.error(`${errorMessage}:`, error);
// 回滚状态
set((state) => ({
...state,
[key]: rollbackState(originalState),
}));
if (silent) {
resolve();
} else {
reject(error);
}
});
});
};
}

View File

@@ -4,7 +4,7 @@
*/
import { ViewStyle } from 'react-native';
import { BREAKPOINTS } from '../hooks/useResponsive';
import { BREAKPOINTS } from '../presentation/hooks/responsive';
/**
* 断点键名