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

@@ -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 {