refactor(core): optimize state management and component rendering performance
Some checks failed
Frontend CI / ota-ios (push) Successful in 1m39s
Frontend CI / ota-android (push) Successful in 1m39s
Frontend CI / build-android-apk (push) Failing after 1m52s
Frontend CI / build-and-push-web (push) Successful in 22m5s

Improve application stability and performance by optimizing Zustand store usage, implementing memoization patterns, and introducing persistent authentication.

- **State Management**:
  - Refactor Zustand selectors to use `useShallow` and `getState()` to prevent unnecessary re-renders and infinite loops in hooks.
  - Implement `persist` middleware for `authStore` to maintain user sessions across restarts.
  - Introduce `buildStateCached` in `themeStore` to reduce redundant theme object computations.
- **Performance & Rendering**:
  - Implement `useMemo` for stable object/array references in `ImageGallery` and list components to prevent expensive re-renders.
  - Replace inline arrow functions with `useCallback` in complex screens like `ChatScreen` and `MessageListScreen`.
  - Optimize `FlashList` usage by providing stable `key` and `extraData` props.
- **Architecture**:
  - Decouple unread count fetching by introducing `useUnreadCountQuery` (React Query) for better caching and synchronization.
  - Add `useChannels` hook to centralize channel data fetching.
  - Refine `SessionGate` logic to allow immediate rendering of authenticated users while verifying in the background.
This commit is contained in:
2026-05-18 00:39:25 +08:00
parent fb67fb6d5b
commit 4fde3e403a
18 changed files with 195 additions and 118 deletions

View File

@@ -12,6 +12,7 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { User } from '../../types';
import { authService, resolveAuthApiError, LoginRequest, RegisterRequest } from '../../services';
import { wsService } from '@/services/core';
@@ -148,15 +149,17 @@ interface AuthState {
fetchCurrentUser: () => Promise<void>;
}
export const useAuthStore = create<AuthState>((set) => ({
isAuthenticated: false,
currentUser: null,
isLoading: false,
error: null,
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
isAuthenticated: false,
currentUser: null,
isLoading: false,
error: null,
// ── 登录 ──
// 流程API → 保存Token → 初始化DB → 持久化userId → 写缓存 → 设置状态
login: async (credentials: LoginRequest) => {
// ── 登录 ──
// 流程API → 保存Token → 初始化DB → 持久化userId → 写缓存 → 设置状态
login: async (credentials: LoginRequest) => {
set({ isLoading: true, error: null });
try {
@@ -396,7 +399,17 @@ export const useAuthStore = create<AuthState>((set) => ({
setLoading: (loading: boolean) => set({ isLoading: loading }),
setError: (error: string | null) => set({ error }),
}));
}),
{
name: 'auth-session',
storage: createJSONStorage(() => AsyncStorage),
partialize: (state) => ({
isAuthenticated: state.isAuthenticated,
currentUser: state.currentUser,
}),
}
)
);
// 导出 selector hooks 以优化性能
export const useCurrentUser = () => useAuthStore((state) => state.currentUser);

View File

@@ -97,13 +97,14 @@ export function useMessages(
loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise<MessageResponse[]>
): UseMessagesReturn {
const normalizedConversationId = normalizeConversationId(conversationId);
const store = useMessageStore();
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
const messages = useMessageStore(
useShallow(state => state.messagesMap.get(normalizedConversationId) || [])
);
const isLoadingMessages = useMessageStore(state => state.loadingMessagesSet.has(normalizedConversationId));
const getActiveConversation = useMessageStore.getState().getActiveConversation;
const setCurrentConversation = useMessageStore.getState().setCurrentConversation;
const [hasMore, setHasMore] = useState(true);
useEffect(() => {
@@ -114,8 +115,8 @@ export function useMessages(
return () => {
// 清理活动会话
if (store.getActiveConversation() === normalizedConversationId) {
store.setCurrentConversation(null);
if (getActiveConversation() === normalizedConversationId) {
setCurrentConversation(null);
}
};
}, [normalizedConversationId]);
@@ -428,11 +429,10 @@ interface UseGroupMutedReturn {
export function useGroupMuted(groupId: string): UseGroupMutedReturn {
// 使用 Zustand selector 直接订阅禁言状态
const isMuted = useMessageStore(state => state.mutedStatusMap.get(groupId) || false);
const store = useMessageStore();
const setMutedStatus = useCallback((muted: boolean) => {
store.setMutedStatus(groupId, muted);
}, [groupId, store]);
useMessageStore.getState().setMutedStatus(groupId, muted);
}, [groupId]);
return {
isMuted,

View File

@@ -16,6 +16,7 @@ import { useShallow } from 'zustand/react/shallow';
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
import { messageManager } from './MessageManager';
import { useMessageStore } from './store';
import { useUnreadCountQuery } from '../../hooks/useUnreadCount';
// ==================== useConversations - 获取会话列表 ====================
@@ -43,16 +44,19 @@ export function useConversations(): UseConversationsReturn {
console.error('[useConversations] 初始化失败:', error);
});
// 冷启动兜底:延迟做一次强制刷新,避免首次因时序问题拿到空列表
const coldStartSyncTimer = setTimeout(() => {
messageManager.refreshConversations(true, 'hooks-initial-refresh').catch(error => {
console.error('[useConversations] 冷启动强制刷新失败:', error);
});
}, 1200);
// 仅在首次初始化时做一次冷启动兜底刷新,避免每次挂载都触发
const store = useMessageStore.getState();
if (!store.isInitialized) {
const coldStartSyncTimer = setTimeout(() => {
messageManager.refreshConversations(true, 'hooks-initial-refresh').catch(error => {
console.error('[useConversations] 冷启动强制刷新失败:', error);
});
}, 1200);
return () => {
clearTimeout(coldStartSyncTimer);
};
return () => {
clearTimeout(coldStartSyncTimer);
};
}
}, []);
// 监听 hasMore 变化
@@ -249,15 +253,11 @@ interface UseUnreadCountReturn {
* 使用 Zustand selector 直接订阅未读数变化
*/
export function useUnreadCount(): UseUnreadCountReturn {
// 使用 Zustand selector 直接订阅未读数
// Use React Query for fetching (caching + auto-refetch), Zustand selectors for reading
useUnreadCountQuery();
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
useEffect(() => {
// 初始化时获取最新未读数
messageManager.fetchUnreadCount();
}, []);
return {
totalUnreadCount,
systemUnreadCount,
@@ -271,14 +271,11 @@ export function useUnreadCount(): UseUnreadCountReturn {
* 使用 Zustand selector 直接订阅
*/
export function useTotalUnreadCount(): number {
// Use React Query for fetching, Zustand selectors for reading
useUnreadCountQuery();
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
useEffect(() => {
// 初始化时获取最新未读数
messageManager.fetchUnreadCount();
}, []);
return totalUnreadCount + systemUnreadCount;
}

View File

@@ -34,6 +34,24 @@ function buildState(
};
}
let _cachedKey = '';
let _cachedState: ReturnType<typeof buildState>;
function buildStateCached(
preference: ThemePreference,
systemScheme: ResolvedScheme
): {
resolvedScheme: ResolvedScheme;
colors: AppColors;
paperTheme: MD3Theme;
} {
const key = `${preference}:${systemScheme}`;
if (_cachedKey === key && _cachedState) return _cachedState;
_cachedKey = key;
_cachedState = buildState(preference, systemScheme);
return _cachedState;
}
type ThemeState = {
preference: ThemePreference;
systemScheme: ResolvedScheme;
@@ -58,14 +76,14 @@ export const useThemeStore = create<ThemeState>((set, get) => ({
preference: 'system',
systemScheme: initialSystemScheme,
hydrated: false,
...buildState('system', initialSystemScheme),
...buildStateCached('system', initialSystemScheme),
setSystemScheme: (systemScheme) => {
const { preference, systemScheme: cur } = get();
if (cur === systemScheme) return;
set({
systemScheme,
...buildState(preference, systemScheme),
...buildStateCached(preference, systemScheme),
});
},
@@ -78,7 +96,7 @@ export const useThemeStore = create<ThemeState>((set, get) => ({
const { systemScheme } = get();
set({
preference,
...buildState(preference, systemScheme),
...buildStateCached(preference, systemScheme),
});
},
@@ -97,7 +115,7 @@ export const useThemeStore = create<ThemeState>((set, get) => ({
preference,
hydrated: true,
systemScheme,
...buildState(preference, systemScheme),
...buildStateCached(preference, systemScheme),
});
},
}));

View File

@@ -5,6 +5,7 @@
*/
import { create } from 'zustand';
import { useShallow } from 'zustand/react/shallow';
import { User, Post, Notification, NotificationBadge } from '../types';
import { PaginatedData } from '@/services/core';
import {
@@ -320,7 +321,7 @@ export const useUserStore = create<UserState>((set, get) => {
// 导出selector hooks以优化性能
export const usePosts = () => useUserStore((state) => state.posts);
export const useNotifications = () => useUserStore((state) => state.notifications);
export const useNotificationBadge = () => useUserStore((state) => state.notificationBadge);
export const useNotificationBadge = () => useUserStore(useShallow((state) => state.notificationBadge));
export const useMessageUnreadCount = () => useUserStore((state) => state.messageUnreadCount);
export const useSearchHistory = () => useUserStore((state) => state.searchHistory);
export const useIsLoadingPosts = () => useUserStore((state) => state.isLoadingPosts);