refactor(core): optimize state management and component rendering performance
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:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user