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

@@ -111,6 +111,11 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
const [chatImages, setChatImages] = useState<ImageGridItem[]>([]);
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
const stableGalleryImages = useMemo(() => chatImages.map((img, i) => ({
id: img.id || img.url || `img-${i}`,
url: img.url || img.uri || ''
})), [chatImages]);
// 图片点击处理函数
const handleImagePress = (images: ImageGridItem[], index: number) => {
setChatImages(images);
@@ -212,6 +217,28 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
jumpToLatestMessages,
setBrowsingHistory,
} = useChatScreen(props);
const stableOnBack = useCallback(() => router.back(), [router]);
const stableOnFocusInput = useCallback(() => {
if (activePanel !== 'none' && activePanel !== 'mention') {
closePanel();
}
}, [activePanel, closePanel]);
const stableFocusTextInput = useCallback(() => textInputRef.current?.focus(), []);
const stableOnLayout = useCallback((e: any) => {
scrollPositionRef.current.viewportHeight = e.nativeEvent.layout.height;
}, []);
const stableOnScrollBeginDrag = useCallback(() => {
isUserDraggingRef.current = true;
handleDismiss();
}, [handleDismiss]);
const stableOnScrollEndDrag = useCallback(() => {
isUserDraggingRef.current = false;
}, []);
const stableOnMomentumScrollEnd = useCallback(() => {
isUserDraggingRef.current = false;
}, []);
const displayMessages = useMemo(() => [...messages].reverse(), [messages]);
const longPressMenuMemberMap = useMemo(() => {
@@ -460,7 +487,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
otherUser={otherUser}
routeGroupName={effectiveGroupName ?? undefined}
typingHint={typingHint}
onBack={props.onEmbeddedBack ? props.onEmbeddedBack : () => router.back()}
onBack={props.onEmbeddedBack || stableOnBack}
onTitlePress={navigateToInfo}
onMorePress={navigateToChatSettings}
onGroupInfoPress={handleGroupInfoPress}
@@ -470,9 +497,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
{/* 消息列表 */}
<View
style={styles.messageListContainer}
onLayout={e => {
scrollPositionRef.current.viewportHeight = e.nativeEvent.layout.height;
}}
onLayout={stableOnLayout}
onTouchEnd={handleDismiss}
>
{loading ? (
@@ -493,16 +518,9 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
scrollEnabled={true}
drawDistance={250}
onScroll={handleMessageListScroll}
onScrollBeginDrag={() => {
isUserDraggingRef.current = true;
handleDismiss();
}}
onScrollEndDrag={() => {
isUserDraggingRef.current = false;
}}
onMomentumScrollEnd={() => {
isUserDraggingRef.current = false;
}}
onScrollBeginDrag={stableOnScrollBeginDrag}
onScrollEndDrag={stableOnScrollEndDrag}
onMomentumScrollEnd={stableOnMomentumScrollEnd}
scrollEventThrottle={16}
onContentSizeChange={handleContentSizeChange}
/>
@@ -568,12 +586,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
pendingAttachments={pendingAttachments}
onRemovePendingAttachment={removePendingAttachment}
onCancelReply={handleCancelReply}
onFocus={() => {
// 输入框获得焦点时,关闭其他面板(但不要关闭键盘)
if (activePanel !== 'none' && activePanel !== 'mention') {
closePanel();
}
}}
onFocus={stableOnFocusInput}
currentUser={currentUser}
otherUser={otherUser}
getSenderInfo={getSenderInfo}
@@ -587,7 +600,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
onInsertEmoji={handleInsertEmoji}
onInsertSticker={handleSendSticker}
onClose={closePanel}
onFocusInput={() => textInputRef.current?.focus()}
onFocusInput={stableFocusTextInput}
/>
</View>
)}
@@ -657,10 +670,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
{/* 图片查看器 */}
<ImageGallery
visible={showImageViewer}
images={chatImages.map(img => ({
id: img.id || img.url || String(Math.random()),
url: img.url || img.uri || ''
}))}
images={stableGalleryImages}
initialIndex={selectedImageIndex}
onClose={handleCloseImageViewer}
enableSave

View File

@@ -152,6 +152,11 @@ export const MessageListScreen: React.FC = () => {
// 系统通知显示状态 - 用于在移动端显示通知页面
const [showNotifications, setShowNotifications] = useState(false);
// Stable callbacks to avoid inline arrows passed to child components
const stableCloseNotifications = useCallback(() => setShowNotifications(false), []);
const stableClearSelectedConversation = useCallback(() => setSelectedConversation(null), []);
const stableCloseScanner = useCallback(() => setScannerVisible(false), []);
// 系统消息会话对象(用于选中状态)
const systemMessageConversation: ConversationResponse = useMemo(() => ({
id: SYSTEM_MESSAGE_CHANNEL_ID,
@@ -197,14 +202,11 @@ export const MessageListScreen: React.FC = () => {
return FLOATING_TAB_BAR_HEIGHT + insets.bottom + spacing.md;
}, [isWideScreen, insets.bottom]);
// 【新架构】页面获得焦点时初始化MessageManager
// 初始化MessageManager(仅首次),焦点时轻量刷新会话列表
useEffect(() => {
if (isFocused) {
messageManager.initialize();
}
}, [isFocused]);
messageManager.initialize();
}, []);
// 【新架构】使用focus刷新hook从ChatScreen返回时自动刷新未读数
useMessageListRefresh();
// 同步未读数到userStore用于TabBar角标显示
@@ -840,7 +842,7 @@ export const MessageListScreen: React.FC = () => {
<View style={styles.chatArea}>
{showNotifications ? (
// 显示系统通知页面
<NotificationsScreen onBack={() => setShowNotifications(false)} />
<NotificationsScreen onBack={stableCloseNotifications} />
) : selectedConversation ? (
// 显示选中会话的聊天内容
<ChatScreen
@@ -849,7 +851,7 @@ export const MessageListScreen: React.FC = () => {
embeddedIsGroupChat={selectedConversation.type === 'group'}
embeddedGroupId={selectedConversation.group ? String(selectedConversation.group.id) : undefined}
embeddedGroupName={selectedConversation.group?.name}
onEmbeddedBack={() => setSelectedConversation(null)}
onEmbeddedBack={stableClearSelectedConversation}
/>
) : (
// 默认占位符
@@ -871,7 +873,7 @@ export const MessageListScreen: React.FC = () => {
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
{showNotifications ? (
// 显示系统通知页面,传入 onBack 回调
<NotificationsScreen onBack={() => setShowNotifications(false)} />
<NotificationsScreen onBack={stableCloseNotifications} />
) : isSearchMode ? (
renderSearchMode()
) : isWideScreen ? (
@@ -882,7 +884,7 @@ export const MessageListScreen: React.FC = () => {
renderConversationList()
)}
{renderActionMenu()}
<QRCodeScanner visible={scannerVisible} onClose={() => setScannerVisible(false)} />
<QRCodeScanner visible={scannerVisible} onClose={stableCloseScanner} />
</SafeAreaView>
);
};