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.
This commit is contained in:
@@ -330,4 +330,4 @@ const TabBar: React.FC<TabBarProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default TabBar;
|
||||
export default React.memo(TabBar);
|
||||
|
||||
@@ -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<DeletionStatusDTO | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
// 响应式布局
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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<void>;
|
||||
handleBlock?: () => Promise<void>;
|
||||
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 (
|
||||
<>
|
||||
<UserProfileHeader
|
||||
user={user}
|
||||
isCurrentUser={isCurrentUser}
|
||||
isBlocked={isBlocked}
|
||||
onFollow={handleFollow}
|
||||
onSettings={handleSettings}
|
||||
onEditProfile={handleEditProfile}
|
||||
onMessage={handleMessage}
|
||||
onBlock={handleBlock}
|
||||
onFollowingPress={handleFollowingPress}
|
||||
onFollowersPress={handleFollowersPress}
|
||||
/>
|
||||
<View style={tabBarContainerStyle}>
|
||||
<TabBar
|
||||
tabs={TABS}
|
||||
activeIndex={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
variant="modern"
|
||||
icons={TAB_ICONS}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
interface UserProfileScreenProps {
|
||||
mode: ProfileMode;
|
||||
userId?: string;
|
||||
@@ -110,50 +171,7 @@ export const UserProfileScreen: React.FC<UserProfileScreenProps> = ({ mode, user
|
||||
);
|
||||
}, [loading, activeTab, mode, isBlockedProfile]);
|
||||
|
||||
// 渲染用户信息头部(与 TabBar 分离,避免切换 tab 时重渲染)
|
||||
const renderUserHeader = useMemo(() => {
|
||||
if (!user) return null;
|
||||
return (
|
||||
<UserProfileHeader
|
||||
user={user}
|
||||
isCurrentUser={isCurrentUser}
|
||||
isBlocked={isBlocked}
|
||||
onFollow={handleFollow}
|
||||
onSettings={handleSettings}
|
||||
onEditProfile={handleEditProfile}
|
||||
onMessage={handleMessage}
|
||||
onBlock={handleBlock}
|
||||
onFollowingPress={handleFollowingPress}
|
||||
onFollowersPress={handleFollowersPress}
|
||||
/>
|
||||
);
|
||||
}, [user, isCurrentUser, isBlocked, handleFollow, handleSettings, handleEditProfile, handleMessage, handleBlock, handleFollowingPress, handleFollowersPress]);
|
||||
|
||||
// 渲染 TabBar
|
||||
const renderTabBar = useMemo(() => (
|
||||
<View style={sharedStyles.tabBarContainer}>
|
||||
<TabBar
|
||||
tabs={TABS}
|
||||
activeIndex={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
variant="modern"
|
||||
icons={TAB_ICONS}
|
||||
/>
|
||||
</View>
|
||||
), [activeTab, setActiveTab, sharedStyles.tabBarContainer]);
|
||||
|
||||
// 渲染 FlashList 头部(用户信息 + TabBar)
|
||||
const renderListHeader = useCallback(() => {
|
||||
if (!user) return null;
|
||||
return (
|
||||
<>
|
||||
{renderUserHeader}
|
||||
{renderTabBar}
|
||||
</>
|
||||
);
|
||||
}, [user, renderUserHeader, renderTabBar]);
|
||||
|
||||
// 未登录/用户不存在状态
|
||||
// 渲染帖子项/用户不存在状态
|
||||
if (mode === 'self' && !currentUser) {
|
||||
return (
|
||||
<SafeAreaView style={sharedStyles.container} edges={hasHeader ? ['bottom'] : ['top', 'bottom']}>
|
||||
@@ -200,7 +218,21 @@ export const UserProfileScreen: React.FC<UserProfileScreenProps> = ({ mode, user
|
||||
/>
|
||||
}
|
||||
>
|
||||
{renderListHeader()}
|
||||
<ProfileListHeader
|
||||
user={user}
|
||||
isCurrentUser={isCurrentUser}
|
||||
isBlocked={isBlocked}
|
||||
handleFollow={handleFollow}
|
||||
handleSettings={handleSettings}
|
||||
handleEditProfile={handleEditProfile}
|
||||
handleMessage={handleMessage}
|
||||
handleBlock={handleBlock}
|
||||
handleFollowingPress={handleFollowingPress}
|
||||
handleFollowersPress={handleFollowersPress}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
tabBarContainerStyle={sharedStyles.tabBarContainer}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
@@ -248,7 +280,23 @@ export const UserProfileScreen: React.FC<UserProfileScreenProps> = ({ mode, user
|
||||
data={currentPosts}
|
||||
renderItem={renderPostItem}
|
||||
keyExtractor={postKeyExtractor}
|
||||
ListHeaderComponent={renderListHeader}
|
||||
ListHeaderComponent={
|
||||
<ProfileListHeader
|
||||
user={user}
|
||||
isCurrentUser={isCurrentUser}
|
||||
isBlocked={isBlocked}
|
||||
handleFollow={handleFollow}
|
||||
handleSettings={handleSettings}
|
||||
handleEditProfile={handleEditProfile}
|
||||
handleMessage={handleMessage}
|
||||
handleBlock={handleBlock}
|
||||
handleFollowingPress={handleFollowingPress}
|
||||
handleFollowersPress={handleFollowersPress}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
tabBarContainerStyle={sharedStyles.tabBarContainer}
|
||||
/>
|
||||
}
|
||||
ListEmptyComponent={renderEmptyPosts}
|
||||
contentContainerStyle={{ paddingBottom: scrollBottomInset }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user