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

14
src/hooks/useChannels.ts Normal file
View File

@@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/react-query';
import { channelService, type ChannelItem } from '@/services/post/channelService';
export const channelKeys = {
all: ['channels'] as const,
};
export function useChannels() {
return useQuery({
queryKey: channelKeys.all,
queryFn: () => channelService.list(),
staleTime: 10 * 60 * 1000,
});
}

View File

@@ -0,0 +1,15 @@
import { useQuery } from '@tanstack/react-query';
import { messageManager } from '@/stores/message/MessageManager';
export const messageKeys = {
unread: ['messages', 'unread'] as const,
};
export function useUnreadCountQuery() {
return useQuery({
queryKey: messageKeys.unread,
queryFn: () => messageManager.fetchUnreadCount(),
staleTime: 30 * 1000,
refetchInterval: 60 * 1000,
});
}

View File

@@ -77,7 +77,7 @@ export const RegisterScreen: React.FC = () => {
resetRegisterData,
goToNextStep,
goToPrevStep,
} = useRegisterStore();
} = useRegisterStore.getState();
// 本地状态
const [loading, setLoading] = React.useState(false);

View File

@@ -29,6 +29,7 @@ import { Post } from '../../types';
import { useUserStore, useHomeTabBarVisibilityStore, useHomeTabPressStore } from '../../stores';
import { useCurrentUser, useIsAuthenticated, useIsVerified, useVerificationStore } from '../../stores/auth';
import { channelService, postService } from '../../services';
import { useChannels } from '../../hooks/useChannels';
import { PostCard, SearchBar, ShareSheet } from '../../components/business';
import type { PostCardAction } from '../../components/business/PostCard';
import { Loading, EmptyState, Text, ImageGallery, ImageGridItem } from '../../components/common';
@@ -215,7 +216,7 @@ function createHomeStyles(colors: AppColors, responsivePadding: number) {
export const HomeScreen: React.FC = () => {
const router = useRouter();
const insets = useSafeAreaInsets();
const { posts: storePosts } = useUserStore();
const storePosts = useUserStore((s) => s.posts);
const currentUser = useCurrentUser();
const isAuthenticated = useIsAuthenticated();
const isVerified = useIsVerified();
@@ -242,13 +243,23 @@ export const HomeScreen: React.FC = () => {
const [sortIndex, setSortIndex] = useState(2);
const [viewMode, setViewMode] = useState<ViewMode>('list');
const [latestCapsules, setLatestCapsules] = useState<LatestCapsule[]>([{ id: '', name: '全部' }]);
const { data: channelList = [] } = useChannels();
const latestCapsules = useMemo<LatestCapsule[]>(
() => [{ id: '', name: '全部' }, ...channelList.map(item => ({ id: item.id, name: item.name }))],
[channelList]
);
const [activeCapsuleId, setActiveCapsuleId] = useState('');
// 图片查看器状态
const [showImageViewer, setShowImageViewer] = useState(false);
const [postImages, setPostImages] = useState<ImageGridItem[]>([]);
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
const stableCloseImageViewer = useCallback(() => setShowImageViewer(false), []);
const stableGalleryImages = useMemo(() => postImages.map((img, i) => ({
id: img.id || img.url || `img-${i}`,
url: img.url || img.uri || ''
})), [postImages]);
// 搜索显示状态(用于内嵌搜索页面)
const [showSearch, setShowSearch] = useState(false);
@@ -587,18 +598,6 @@ export const HomeScreen: React.FC = () => {
});
}, []);
useEffect(() => {
const loadChannels = async () => {
const list = await channelService.list();
const capsules: LatestCapsule[] = [
{ id: '', name: '全部' },
...list.map(item => ({ id: item.id, name: item.name })),
];
setLatestCapsules(capsules);
};
loadChannels();
}, []);
// 跳转到搜索页(使用内嵌模式,不再依赖导航)
const handleSearchPress = () => {
setShowSearch(true);
@@ -811,9 +810,10 @@ export const HomeScreen: React.FC = () => {
const renderResponsiveGrid = () => {
return (
<FlashList
key={`${listKey}_grid`}
key="home-grid"
ref={scrollViewRef as any}
data={displayPosts}
extraData={listKey}
renderItem={renderGridItem}
keyExtractor={keyExtractor}
numColumns={gridColumns}
@@ -865,9 +865,10 @@ export const HomeScreen: React.FC = () => {
// 移动端和宽屏都使用单列 FlashList宽屏下居中显示
return (
<FlashList
key={listKey}
key="home-list"
ref={flashListRef}
data={displayPosts}
extraData={listKey}
renderItem={renderPostList}
keyExtractor={keyExtractor}
contentContainerStyle={{
@@ -1062,12 +1063,9 @@ export const HomeScreen: React.FC = () => {
{/* 图片查看器 */}
<ImageGallery
visible={showImageViewer}
images={postImages.map(img => ({
id: img.id || img.url || String(Math.random()),
url: img.url || img.uri || ''
}))}
images={stableGalleryImages}
initialIndex={selectedImageIndex}
onClose={() => setShowImageViewer(false)}
onClose={stableCloseImageViewer}
enableSave
/>

View File

@@ -187,7 +187,8 @@ export function MarketView({
renderItem={renderItem}
keyExtractor={keyExtractor}
numColumns={numColumns}
key={`market_${numColumns}`}
key="market-list"
extraData={numColumns}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
refreshControl={

View File

@@ -1084,6 +1084,12 @@ export const PostDetailScreen: React.FC = () => {
}));
}, [post?.images]);
// 为 ImageGallery 提供稳定的图片列表,避免 Math.random() 在每次渲染时生成新 ID
const stableGalleryImages = useMemo(() => allImages.map((img, i) => ({
id: img.id || img.url || `img-${i}`,
url: img.url || img.uri || ''
})), [allImages]);
// 渲染帖子头部 - 小红书/微博风格
const renderPostHeader = useCallback(() => {
if (!post) return null;
@@ -1740,10 +1746,7 @@ export const PostDetailScreen: React.FC = () => {
{/* 图片预览 ImageGallery */}
<ImageGallery
visible={showImageModal}
images={allImages.map(img => ({
id: img.id || img.url || String(Math.random()),
url: img.url || img.uri || ''
}))}
images={stableGalleryImages}
initialIndex={selectedImageIndex}
onClose={() => setShowImageModal(false)}
enableSave
@@ -1815,10 +1818,7 @@ export const PostDetailScreen: React.FC = () => {
{/* 图片预览 ImageGallery */}
<ImageGallery
visible={showImageModal}
images={allImages.map(img => ({
id: img.id || img.url || String(Math.random()),
url: img.url || img.uri || ''
}))}
images={stableGalleryImages}
initialIndex={selectedImageIndex}
onClose={() => setShowImageModal(false)}
enableSave

View File

@@ -44,7 +44,8 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
const styles = useMemo(() => createSearchScreenStyles(colors), [colors]);
const router = useRouter();
const insets = useSafeAreaInsets();
const { searchHistory: history, addSearchHistory, clearSearchHistory } = useUserStore();
const history = useUserStore((state) => state.searchHistory);
const { addSearchHistory, clearSearchHistory } = useUserStore.getState();
// 使用响应式 hook
const {

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>
);
};

View File

@@ -33,7 +33,7 @@ const FollowListScreen: React.FC = () => {
const type = typeParam === 'followers' ? 'followers' : 'following';
const { currentUser } = useAuthStore();
const { followUser, unfollowUser } = useUserStore();
const { followUser, unfollowUser } = useUserStore.getState();
// 响应式布局
const { isWideScreen, isDesktop, width } = useResponsive();

View File

@@ -72,7 +72,8 @@ export const useUserProfile = (options: UseUserProfileOptions): UseUserProfileRe
const insets = useSafeAreaInsets();
const router = useRouter();
const { followUser, unfollowUser, posts: storePosts } = useUserStore();
const storePosts = useUserStore((state) => state.posts);
const { followUser, unfollowUser } = useUserStore.getState();
const currentUser = useCurrentUser();
// 状态

View File

@@ -634,6 +634,11 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
}));
}, [images]);
const stableTradeGalleryImages = useMemo(() => tradeImages.map((img, i) => ({
id: img.id || img.url || `trade-img-${i}`,
url: img.url || '',
})), [tradeImages]);
const handleImagePress = useCallback((allImages: any[], index: number) => {
setSelectedImageIndex(index);
setShowImageModal(true);
@@ -965,10 +970,7 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
{/* ─── 图片预览 ─── */}
<ImageGallery
visible={showImageModal}
images={tradeImages.map(img => ({
id: img.id || img.url || String(Math.random()),
url: img.url || '',
}))}
images={stableTradeGalleryImages}
initialIndex={selectedImageIndex}
onClose={() => setShowImageModal(false)}
enableSave

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);