From 7a17323e8dd3900743e0fa063b3fed7438ee2923 Mon Sep 17 00:00:00 2001 From: lan Date: Mon, 18 May 2026 01:06:46 +0800 Subject: [PATCH] perf(profile): improve rendering performance and selector efficiency Optimize profile-related screens and components by implementing memoization and granular Zustand selectors to reduce unnecessary re-renders. - **Performance Optimization**: - Wrap `TabBar` in `React.memo` to prevent re-renders when parent components update. - Refactor `UserProfileScreen` to use a memoized `ProfileListHeader` component, decoupling the user header from tab state changes. - **State Management**: - Replace object destructuring from `useAuthStore` with granular selectors (e.g., `useAuthStore((s) => s.logout)`) in `AccountDeletionScreen`, `EditProfileScreen`, `FollowListScreen`, and `SettingsScreen` to prevent re-renders on unrelated store changes. - **Code Cleanup**: - Remove redundant case logic in `useUserProfile` hook. --- src/components/business/TabBar.tsx | 2 +- src/screens/profile/AccountDeletionScreen.tsx | 2 +- src/screens/profile/EditProfileScreen.tsx | 3 +- src/screens/profile/FollowListScreen.tsx | 2 +- src/screens/profile/SettingsScreen.tsx | 2 +- src/screens/profile/UserProfileScreen.tsx | 142 ++++++++++++------ src/screens/profile/useUserProfile.ts | 9 +- 7 files changed, 102 insertions(+), 60 deletions(-) diff --git a/src/components/business/TabBar.tsx b/src/components/business/TabBar.tsx index 1eb9fe4..7cad28f 100644 --- a/src/components/business/TabBar.tsx +++ b/src/components/business/TabBar.tsx @@ -330,4 +330,4 @@ const TabBar: React.FC = ({ ); }; -export default TabBar; +export default React.memo(TabBar); diff --git a/src/screens/profile/AccountDeletionScreen.tsx b/src/screens/profile/AccountDeletionScreen.tsx index 07387fd..ba6bd8e 100644 --- a/src/screens/profile/AccountDeletionScreen.tsx +++ b/src/screens/profile/AccountDeletionScreen.tsx @@ -180,7 +180,7 @@ export const AccountDeletionScreen: React.FC = () => { const router = useRouter(); const { isMobile } = useResponsive(); const insets = useSafeAreaInsets(); - const { logout } = useAuthStore(); + const logout = useAuthStore((s) => s.logout); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); diff --git a/src/screens/profile/EditProfileScreen.tsx b/src/screens/profile/EditProfileScreen.tsx index a950fee..46240a4 100644 --- a/src/screens/profile/EditProfileScreen.tsx +++ b/src/screens/profile/EditProfileScreen.tsx @@ -279,7 +279,8 @@ export const EditProfileScreen: React.FC = () => { const styles = useMemo(() => createEditProfileStyles(colors), [colors]); const navigation = useNavigation(); const router = useRouter(); - const { currentUser, updateUser } = useAuthStore(); + const currentUser = useAuthStore((s) => s.currentUser); + const updateUser = useAuthStore((s) => s.updateUser); const { isWideScreen, isMobile, width } = useResponsive(); const responsivePadding = useResponsiveSpacing({ xs: 0, sm: 0, md: 0, lg: 24, xl: 32 }); const insets = useSafeAreaInsets(); diff --git a/src/screens/profile/FollowListScreen.tsx b/src/screens/profile/FollowListScreen.tsx index 67be167..253cbeb 100644 --- a/src/screens/profile/FollowListScreen.tsx +++ b/src/screens/profile/FollowListScreen.tsx @@ -32,7 +32,7 @@ const FollowListScreen: React.FC = () => { const { userId = '', type: typeParam } = useLocalSearchParams<{ userId?: string; type?: string }>(); const type = typeParam === 'followers' ? 'followers' : 'following'; - const { currentUser } = useAuthStore(); + const currentUser = useAuthStore((s) => s.currentUser); const { followUser, unfollowUser } = useUserStore.getState(); // 响应式布局 diff --git a/src/screens/profile/SettingsScreen.tsx b/src/screens/profile/SettingsScreen.tsx index d13a0ae..eca8ae2 100644 --- a/src/screens/profile/SettingsScreen.tsx +++ b/src/screens/profile/SettingsScreen.tsx @@ -218,7 +218,7 @@ function createSettingsStyles(colors: AppColors) { export const SettingsScreen: React.FC = () => { const router = useRouter(); - const { logout } = useAuthStore(); + const logout = useAuthStore((s) => s.logout); const insets = useSafeAreaInsets(); const { isMobile } = useResponsive(); const responsivePadding = useResponsiveSpacing({ xs: 0, sm: 0, md: 0, lg: 24, xl: 32 }); diff --git a/src/screens/profile/UserProfileScreen.tsx b/src/screens/profile/UserProfileScreen.tsx index 80c9e11..b6446c7 100644 --- a/src/screens/profile/UserProfileScreen.tsx +++ b/src/screens/profile/UserProfileScreen.tsx @@ -5,7 +5,7 @@ * 支持桌面端双栏布局 */ -import React, { useCallback, useMemo, useRef } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { View, RefreshControl, @@ -20,6 +20,67 @@ import { Loading, EmptyState, ResponsiveContainer } from '../../components/commo import { useResponsive } from '../../hooks'; import { useUserProfile, ProfileMode, TABS, TAB_ICONS, createSharedProfileStyles } from './useUserProfile'; +// 稳定的头部组件:切换 tab 时不会导致 UserProfileHeader 重渲染 +// 因为 activeTab 只影响 TabBar,UserProfileHeader 的 props 不变 +interface ProfileListHeaderProps { + user: any; + isCurrentUser: boolean; + isBlocked: boolean; + handleFollow: () => void; + handleSettings?: () => void; + handleEditProfile?: () => void; + handleMessage?: () => Promise; + handleBlock?: () => Promise; + handleFollowingPress: () => void; + handleFollowersPress: () => void; + activeTab: number; + setActiveTab: (tab: number) => void; + tabBarContainerStyle: any; +} + +const ProfileListHeader = React.memo(function ProfileListHeader({ + user, + isCurrentUser, + isBlocked, + handleFollow, + handleSettings, + handleEditProfile, + handleMessage, + handleBlock, + handleFollowingPress, + handleFollowersPress, + activeTab, + setActiveTab, + tabBarContainerStyle, +}: ProfileListHeaderProps) { + if (!user) return null; + return ( + <> + + + + + + ); +}); + interface UserProfileScreenProps { mode: ProfileMode; userId?: string; @@ -110,50 +171,7 @@ export const UserProfileScreen: React.FC = ({ mode, user ); }, [loading, activeTab, mode, isBlockedProfile]); - // 渲染用户信息头部(与 TabBar 分离,避免切换 tab 时重渲染) - const renderUserHeader = useMemo(() => { - if (!user) return null; - return ( - - ); - }, [user, isCurrentUser, isBlocked, handleFollow, handleSettings, handleEditProfile, handleMessage, handleBlock, handleFollowingPress, handleFollowersPress]); - - // 渲染 TabBar - const renderTabBar = useMemo(() => ( - - - - ), [activeTab, setActiveTab, sharedStyles.tabBarContainer]); - - // 渲染 FlashList 头部(用户信息 + TabBar) - const renderListHeader = useCallback(() => { - if (!user) return null; - return ( - <> - {renderUserHeader} - {renderTabBar} - - ); - }, [user, renderUserHeader, renderTabBar]); - - // 未登录/用户不存在状态 + // 渲染帖子项/用户不存在状态 if (mode === 'self' && !currentUser) { return ( @@ -200,7 +218,21 @@ export const UserProfileScreen: React.FC = ({ mode, user /> } > - {renderListHeader()} + @@ -248,7 +280,23 @@ export const UserProfileScreen: React.FC = ({ mode, user data={currentPosts} renderItem={renderPostItem} keyExtractor={postKeyExtractor} - ListHeaderComponent={renderListHeader} + ListHeaderComponent={ + + } ListEmptyComponent={renderEmptyPosts} contentContainerStyle={{ paddingBottom: scrollBottomInset }} showsVerticalScrollIndicator={false} diff --git a/src/screens/profile/useUserProfile.ts b/src/screens/profile/useUserProfile.ts index bd889b6..a2b44bb 100644 --- a/src/screens/profile/useUserProfile.ts +++ b/src/screens/profile/useUserProfile.ts @@ -315,7 +315,7 @@ export const useUserProfile = (options: UseUserProfileOptions): UseUserProfileRe handleUserPress(post.author.id); } break; -case 'like': + case 'like': case 'unlike': post.is_liked ? postSyncService.unlikePost(post.id) : postSyncService.likePost(post.id); break; @@ -323,13 +323,6 @@ case 'like': handlePostPress(post.id, true); break; case 'bookmark': - case 'unbookmark': - post.is_favorited ? postSyncService.unfavoritePost(post.id) : postSyncService.favoritePost(post.id); - break; - case 'comment': - handlePostPress(post.id, true); - break; - case 'bookmark': case 'unbookmark': post.is_favorited ? postSyncService.unfavoritePost(post.id) : postSyncService.favoritePost(post.id); break;