Expo Router + Zustand 的现代化 React Native 应用架构:构建可维护的跨平台应用

前言

随着 React Native 生态的不断发展,开发者们面临着越来越多的技术选择。如何在保持开发效率的同时,构建出高性能、可维护、易扩展的移动应用架构,是每个团队都需要思考的问题。

本文将深入探讨如何基于 Expo Router 和 Zustand 构建现代化的 React Native 应用架构,包括路由管理、状态管理、数据持久化、国际化等核心功能的实现方案。
在这里插入图片描述

项目背景

这是一个医疗管理应用,具有以下特点:

  • 多平台支持:需要同时支持 iOS 和 Android
  • 复杂业务逻辑:包含用户管理、检查记录、治疗计划等多个业务模块
  • 数据持久化:需要本地存储用户信息和检查数据
  • 国际化支持:需要支持多语言切换
  • 热更新:需要支持代码热更新功能

技术架构概览

1. 整体架构设计

Expo Router
File-based Routing
Screen Components
Business Components
UI Components
Zustand Stores
Global State
Persistent State
Local State
Services Layer
API Services
Storage Services
Device Services
Utils & Hooks
Custom Hooks
Utility Functions
Constants

2. 目录结构设计

src/
├── app/                    # Expo Router 路由文件
│   ├── (tabs)/            # Tab 导航组
│   ├── login/             # 登录相关页面
│   ├── medical/           # 核心业务页面
│   └── _layout.tsx        # 根布局
├── components/            # 组件库
│   ├── ui/                # 基础 UI 组件
│   ├── business/          # 业务组件
│   └── icons/             # 图标组件
├── stores/                # Zustand 状态管理
├── services/              # 服务层
├── hooks/                 # 自定义 Hooks
├── libs/                  # 工具库
├── constants/             # 常量定义
├── types/                 # TypeScript 类型
└── languages/             # 国际化资源

核心功能实现

1. Expo Router 文件路由系统

1.1 根布局设计
// app/_layout.tsx
import { DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { Stack, useNavigationContainerRef, usePathname, useRouter, useSegments } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Text, TextInput } from 'react-native';
import 'react-native-reanimated';
import '../global.css';

import { I18nextProvider } from 'react-i18next';
import i18n from '@/libs/i18n';
import GlobalToastGroup from '@/components/ui/Toast/Group';
import { PortalProvider } from '@gorhom/portal';
import GlobalConfirmModal from '@/components/ui/Modal/GlobalConfirmModal';

import { StatusBar } from 'expo-status-bar';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import React from 'react';
import {
    useStatusBarStore,
    useSubscription,
    online$,
    useScreenOrientationLock,
    useNavigationBarStore,
    useOnline,
    currentRoute$,
    useAppState,
    IS_IOS,
    DEVICE_BRAND,
} from '@ife/react-native-common-lib';
import { useToastStore } from '@/stores/toast';
import { useConfirmModalStore } from '@/stores/confirm-modal';
import { useLoginTokenStore } from '@/stores/login-token';
import { usePatientInfoStore } from '@/stores/patient-info';
import { useCheckVersion } from '@/hooks/useCheckVersion';
import { UpdateProvider, Pushy } from 'react-native-update';

// 防止字体缩放
(Text as any).defaultProps = {
    ...(Text as any).defaultProps,
    allowFontScaling: false,
};

(TextInput as any).defaultProps = {
    ...(TextInput as any).defaultProps,
    allowFontScaling: false,
};

// 防止启动屏自动隐藏
SplashScreen.preventAutoHideAsync();

const WHITELIST_ROUTES = ['/login/supplemental-info', '/login/phone-binding'];

function StackNavigator() {
    const statusBarStyle = useStatusBarStore(state => state.statusBarStyle);
    const statusBarHidden = useStatusBarStore(state => state.statusBarHidden);
    const setStatusBarStyle = useStatusBarStore(state => state.setStatusBarStyle);
    const setNavigationBarBgColor = useNavigationBarStore(state => state.setNavigationBarBgColor);
    const uuid = useLoginTokenStore(state => state.uuid);
    const { gestureEnabled } = useScreenGesture();
    const pathname = usePathname();
    const navigationRef = useNavigationContainerRef();
    const appState = useAppState();
    const { fetchUnreadMessageCounts } = useMessageStore();
    const segments = useSegments();

    const router = useRouter();
    const accessToken = useLoginTokenStore(state => state.accessToken);
    const appInitConsent = useAppInitConsentStore(state => state.hasReadAgreement);
    const patientInfo = usePatientInfoStore(state => state.patientInfo);
    const prevPathNameRef = useRef<string>('');
    
    useCheckVersion();

    // 路由守卫逻辑
    useEffect(() => {
        if (pathname !== '/') return;
        if (!appInitConsent) {
            router.replace('/agreement');
            return;
        }

        initClientId();
        if (!accessToken) {
            router.replace('/login');
            return;
        }

        if (accessToken && !patientInfo.nickname && !WHITELIST_ROUTES.includes(pathname)) {
            router.replace('/login/supplemental-info');
            return;
        }
        router.replace('/(tabs)');
    }, [accessToken, appInitConsent, pathname, patientInfo.nickname, router]);

    // 页面访问统计
    useEffect(() => {
        if (!navigationRef.current) return;
        const params = navigationRef.current?.getCurrentRoute()?.params as Record<string, string>;
        currentRoute$.set({
            pathname,
            params,
        });
        const prevPathNameCN = prevPathNameRef.current ? routerList.find(item => item.path === prevPathNameRef.current)?.name || '' : '';
        Log.pv({
            event: 'view',
            prevPathName: prevPathNameRef.current,
            prevPathNameCN,
        });
        prevPathNameRef.current = pathname;
    }, [navigationRef, pathname]);

    return (
        <>
            <StatusBar style={statusBarStyle} hidden={statusBarHidden} translucent={true} backgroundColor="transparent" />
            <Stack
                screenOptions={{
                    headerShown: false,
                    gestureEnabled,
                }}
            >
                <Stack.Screen name="(tabs)" />
                <Stack.Screen name="login" />
                <Stack.Screen name="medical" />
                <Stack.Screen name="agreement" />
                <Stack.Screen name="message-details" />
                <Stack.Screen name="demo" />
                <Stack.Screen name="camera" initialParams={{ mode: undefined } as CameraScreenParams} />
                <Stack.Screen name="webview" initialParams={{ url: '', title: '' } as WebviewScreenParams} />
                <Stack.Screen name="changelog" />
                <Stack.Screen name="+not-found" />
            </Stack>
        </>
    );
}

export default function RootLayout() {
    const { toasts } = useToastStore();
    const { confirmModalConfig } = useConfirmModalStore();
    const { loadingConfig } = useLoadingStore();
    const online = useSubscription(online$);
    const network = useNetworkState();
    const [isHydrated, setIsHydrated] = useState(false);

    // 网络状态监控
    useOnline('https://example.com/favicon.svg');

    // 屏幕方向锁定
    useScreenOrientationLock();

    // 热更新配置
    const pushyClient = new Pushy({
        appKey: (IS_IOS ? process.env.EXPO_PUBLIC_PUSHY_IOS_KEY : process.env.EXPO_PUBLIC_PUSHY_ANDROID_KEY) as string,
        checkStrategy: 'onAppStart',
        updateStrategy: IS_RELEASE_PACKAGE ? 'silentAndLater' : 'alwaysAlert',
        logger({ type, data }) {
            Log.hotUpdate({
                event: 'hotUpdateType',
                value: type,
                other: JSON.stringify(data),
            });
        },
    });

    // Store 水合状态检查
    useEffect(() => {
        let isActive = true;

        const tokenHydrated = useLoginTokenStore.persist?.hasHydrated?.();
        const consentHydrated = useAppInitConsentStore.persist?.hasHydrated?.();

        if (tokenHydrated && consentHydrated) {
            setIsHydrated(true);
            return;
        }

        const unsubToken = useLoginTokenStore.persist?.onFinishHydration?.(() => {
            if (isActive && useAppInitConsentStore.persist?.hasHydrated?.()) {
                setIsHydrated(true);
            }
        });

        const unsubConsent = useAppInitConsentStore.persist?.onFinishHydration?.(() => {
            if (isActive && useLoginTokenStore.persist?.hasHydrated?.()) {
                setIsHydrated(true);
            }
        });

        return () => {
            isActive = false;
            unsubToken?.();
            unsubConsent?.();
        };
    }, []);

    useEffect(() => {
        if (isHydrated) {
            SplashScreen.hideAsync();
        }
    }, [isHydrated]);

    return (
        <UpdateProvider client={pushyClient}>
            <SafeAreaProvider>
                <I18nextProvider i18n={i18n}>
                    <PortalProvider>
                        <ThemeProvider value={DefaultTheme}>
                            <StackNavigator />
                            <GlobalToastGroup toasts={toasts} />
                            <GlobalConfirmModal confirmModalConfig={confirmModalConfig} />
                            <GlobalLoading loadinglConfig={loadingConfig} visible={loadingConfig.visible} />
                            <PermissionPrompt />
                        </ThemeProvider>
                    </PortalProvider>
                </I18nextProvider>
            </SafeAreaProvider>
        </UpdateProvider>
    );
}
1.2 Tab 导航设计
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import React from 'react';
import { Pressable, StyleSheet } from 'react-native';
import COLORS from '@/constants/colors';
import { useTranslation } from 'react-i18next';
import TabBarIcon from '@/components/ui/TabBarIcon';
import TabBarMessage from '@/components/business/TabBarMessage';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function TabLayout() {
    const { t } = useTranslation();
    const insets = useSafeAreaInsets();
    
    return (
        <Tabs
            screenOptions={{
                headerShown: false,
                tabBarActiveTintColor: COLORS.brand.primary,
                tabBarInactiveTintColor: COLORS.text.secondary,
                tabBarStyle: styles.tabBarStyle,
                tabBarItemStyle: styles.tabBarItemStyle,
                tabBarIconStyle: styles.tabBarIconStyle,
                tabBarLabelStyle: styles.tabBarLabelStyle,
                tabBarButton: props => (
                    <Pressable
                        {...props}
                        android_ripple={{ color: 'transparent' }}
                    />
                ),
            }}
            safeAreaInsets={{
                top: 0,
                bottom: Math.max(12, insets.bottom),
            }}
        >
            <Tabs.Screen
                name="index"
                options={{
                    title: t('tab_screen_index'),
                    tabBarIcon: ({ focused }) => <TabBarIcon name={focused ? 'index' : 'index-outline'} />,
                }}
            />
            <Tabs.Screen
                name="message"
                options={{
                    title: t('message.message'),
                    tabBarBadgeStyle: { backgroundColor: '#FF5954', fontSize: 10, width: 16, height: 16 },
                    tabBarIcon: props => <TabBarMessage {...props} />,
                }}
            />
            <Tabs.Screen
                name="personal"
                options={{
                    title: t('tab_screen_personal'),
                    tabBarIcon: ({ focused }) => <TabBarIcon name={focused ? 'personal' : 'personal-outline'} />,
                }}
            />
        </Tabs>
    );
}

2. Zustand 状态管理架构

2.1 登录状态管理
// src/stores/login-token.ts
import { STORAGE_KEY } from '@/constants/async-storage-keys';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

type State = API.LoginResData;

type Actions = {
    setLoginToken: (data: API.LoginResData) => void;
    removeToken: () => void;
};

/**
 * 用于登录后,存储 token
 * @description 使用了 persist 和 createJSONStorage 中间件,数据会持久化存储在 AsyncStorage 中
 */
export const useLoginTokenStore = create<State & Actions>()(
    persist(
        (set, get) => ({
            accessToken: '',
            refreshToken: '',
            uuid: '',
            setLoginToken: (data: API.LoginResData) => {
                // 兼容刷新token uuid不返回问题
                const uuid = data.uuid ? data.uuid : get().uuid;
                set(() => ({ ...data, uuid }));
            },
            removeToken: () => set(() => ({ accessToken: '', refreshToken: '', uuid: '' })),
        }),
        {
            name: STORAGE_KEY['LOGIN_TOKEN'],
            storage: createJSONStorage(() => AsyncStorage),
        },
    ),
);

export const $loginTokenStore = useLoginTokenStore;
2.2 患者信息管理
// src/stores/patient-info.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { STORAGE_KEY } from '@/constants/async-storage-keys';

interface PatientInfo {
    nickname: string;
    avatar: string;
    phone: string;
    gender: number;
    birthday: string;
    // ... 其他患者信息字段
}

interface PatientInfoState {
    patientInfo: PatientInfo;
    setPatientInfo: (info: Partial<PatientInfo>) => void;
    clearPatientInfo: () => void;
}

const defaultPatientInfo: PatientInfo = {
    nickname: '',
    avatar: '',
    phone: '',
    gender: 0,
    birthday: '',
};

export const usePatientInfoStore = create<PatientInfoState>()(
    persist(
        (set) => ({
            patientInfo: defaultPatientInfo,
            setPatientInfo: (info) => 
                set((state) => ({
                    patientInfo: { ...state.patientInfo, ...info }
                })),
            clearPatientInfo: () => 
                set(() => ({ patientInfo: defaultPatientInfo })),
        }),
        {
            name: STORAGE_KEY['PATIENT_INFO'],
            storage: createJSONStorage(() => AsyncStorage),
        },
    ),
);
2.3 全局 UI 状态管理
// src/stores/toast.ts
import { create } from 'zustand';

export interface Toast {
    id: string;
    type: 'success' | 'error' | 'warning' | 'info';
    title: string;
    message?: string;
    duration?: number;
    action?: {
        label: string;
        onPress: () => void;
    };
}

interface ToastState {
    toasts: Toast[];
    addToast: (toast: Omit<Toast, 'id'>) => void;
    removeToast: (id: string) => void;
    clearToasts: () => void;
}

export const useToastStore = create<ToastState>((set, get) => ({
    toasts: [],
    addToast: (toast) => {
        const id = Math.random().toString(36).substr(2, 9);
        const newToast = { ...toast, id };
        
        set((state) => ({
            toasts: [...state.toasts, newToast]
        }));
        
        // 自动移除
        if (toast.duration !== 0) {
            setTimeout(() => {
                get().removeToast(id);
            }, toast.duration || 3000);
        }
    },
    removeToast: (id) => 
        set((state) => ({
            toasts: state.toasts.filter(toast => toast.id !== id)
        })),
    clearToasts: () => set(() => ({ toasts: [] })),
}));

// src/stores/loading.ts
interface LoadingState {
    loadingConfig: {
        visible: boolean;
        text?: string;
        mask?: boolean;
    };
    showLoading: (text?: string, mask?: boolean) => void;
    hideLoading: () => void;
}

export const useLoadingStore = create<LoadingState>((set) => ({
    loadingConfig: {
        visible: false,
        text: '',
        mask: true,
    },
    showLoading: (text = '', mask = true) => 
        set(() => ({
            loadingConfig: { visible: true, text, mask }
        })),
    hideLoading: () => 
        set(() => ({
            loadingConfig: { visible: false, text: '', mask: true }
        })),
}));

3. 自定义 Hooks 设计

3.1 网络状态 Hook
// src/hooks/useNetworkState.ts
import { useEffect, useState } from 'react';
import NetInfo from '@react-native-community/netinfo';
import { useSubscription } from '@ife/react-native-common-lib';
import { online$ } from '@ife/react-native-common-lib';

export const useNetworkState = () => {
    const [isConnected, setIsConnected] = useState<boolean | null>(null);
    const [connectionType, setConnectionType] = useState<string | null>(null);
    const online = useSubscription(online$);

    useEffect(() => {
        const unsubscribe = NetInfo.addEventListener(state => {
            setIsConnected(state.isConnected);
            setConnectionType(state.type);
        });

        return unsubscribe;
    }, []);

    return {
        isConnected,
        connectionType,
        online,
        isOffline: isConnected === false,
    };
};
3.2 版本检查 Hook
// src/hooks/useCheckVersion.ts
import { useEffect } from 'react';
import { useToastStore } from '@/stores/toast';
import { useLoadingStore } from '@/stores/loading';
import { checkAppVersion } from '@/services/v1/app';

export const useCheckVersion = () => {
    const { addToast } = useToastStore();
    const { showLoading, hideLoading } = useLoadingStore();

    useEffect(() => {
        const checkVersion = async () => {
            try {
                showLoading('检查更新中...');
                const result = await checkAppVersion();
                
                if (result.hasUpdate) {
                    addToast({
                        type: 'info',
                        title: '发现新版本',
                        message: `版本 ${result.version} 已发布,请及时更新`,
                        duration: 5000,
                        action: {
                            label: '立即更新',
                            onPress: () => {
                                // 跳转到应用商店或下载页面
                            }
                        }
                    });
                }
            } catch (error) {
                console.error('版本检查失败:', error);
            } finally {
                hideLoading();
            }
        };

        checkVersion();
    }, []);
};
3.3 屏幕手势 Hook
// src/hooks/useScreenGesture.ts
import { useEffect, useState } from 'react';
import { usePathname } from 'expo-router';

interface ScreenGestureConfig {
    gestureEnabled: boolean;
    swipeBackEnabled: boolean;
}

export const useScreenGesture = (): ScreenGestureConfig => {
    const pathname = usePathname();
    const [gestureEnabled, setGestureEnabled] = useState(true);
    const [swipeBackEnabled, setSwipeBackEnabled] = useState(true);

    useEffect(() => {
        // 根据路由配置手势行为
        const disableGestureRoutes = ['/camera', '/preview'];
        const disableSwipeBackRoutes = ['/login', '/agreement'];

        setGestureEnabled(!disableGestureRoutes.some(route => pathname.includes(route)));
        setSwipeBackEnabled(!disableSwipeBackRoutes.some(route => pathname.includes(route)));
    }, [pathname]);

    return {
        gestureEnabled,
        swipeBackEnabled,
    };
};

4. 国际化支持

4.1 i18n 配置
// src/libs/i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as Localization from 'expo-localization';
import zhCN from '@/languages/zh-CN.json';

const resources = {
    'zh-CN': {
        translation: zhCN,
    },
    'en-US': {
        translation: {
            // 英文翻译
        },
    },
};

i18n
    .use(initReactI18next)
    .init({
        resources,
        lng: Localization.locale,
        fallbackLng: 'zh-CN',
        interpolation: {
            escapeValue: false,
        },
        react: {
            useSuspense: false,
        },
    });

export default i18n;
4.2 语言切换组件
// src/components/business/LanguageSwitcher.tsx
import React from 'react';
import { Pressable, Text } from 'react-native';
import { useTranslation } from 'react-i18next';
import { useToastStore } from '@/stores/toast';

const LanguageSwitcher: React.FC = () => {
    const { i18n } = useTranslation();
    const { addToast } = useToastStore();

    const switchLanguage = (lng: string) => {
        i18n.changeLanguage(lng);
        addToast({
            type: 'success',
            title: '语言切换成功',
            duration: 2000,
        });
    };

    return (
        <Pressable onPress={() => switchLanguage(i18n.language === 'zh-CN' ? 'en-US' : 'zh-CN')}>
            <Text>{i18n.language === 'zh-CN' ? 'English' : '中文'}</Text>
        </Pressable>
    );
};

export default LanguageSwitcher;

5. 错误处理与监控

5.1 全局错误处理
// src/libs/error-handler.ts
import { fundebug } from '@/libs/report/fundebug';
import { useToastStore } from '@/stores/toast';

class ErrorHandler {
    private toastStore: any;

    init(toastStore: any) {
        this.toastStore = toastStore;
    }

    handleError(error: Error, context?: string) {
        console.error('Error occurred:', error);
        
        // 上报错误
        fundebug.notifyError(error, {
            context,
            timestamp: new Date().toISOString(),
        });

        // 显示用户友好的错误提示
        this.toastStore?.addToast({
            type: 'error',
            title: '操作失败',
            message: this.getErrorMessage(error),
        });
    }

    private getErrorMessage(error: Error): string {
        if (error.message.includes('Network')) {
            return '网络连接异常,请检查网络设置';
        }
        if (error.message.includes('Timeout')) {
            return '请求超时,请稍后重试';
        }
        return '系统异常,请稍后重试';
    }
}

export const errorHandler = new ErrorHandler();
5.2 网络请求拦截器
// src/services/api-interceptor.ts
import axios from 'axios';
import { useLoginTokenStore } from '@/stores/login-token';
import { errorHandler } from '@/libs/error-handler';

const apiClient = axios.create({
    baseURL: process.env.EXPO_PUBLIC_API_ROOT,
    timeout: 10000,
});

// 请求拦截器
apiClient.interceptors.request.use(
    (config) => {
        const token = useLoginTokenStore.getState().accessToken;
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    },
    (error) => {
        errorHandler.handleError(error, 'Request Interceptor');
        return Promise.reject(error);
    }
);

// 响应拦截器
apiClient.interceptors.response.use(
    (response) => response,
    (error) => {
        if (error.response?.status === 401) {
            // Token 过期,清除登录状态
            useLoginTokenStore.getState().removeToken();
        }
        errorHandler.handleError(error, 'Response Interceptor');
        return Promise.reject(error);
    }
);

export default apiClient;

性能优化策略

1. 组件懒加载

// src/components/LazyComponent.tsx
import React, { Suspense, lazy } from 'react';
import { ActivityIndicator, View } from 'react-native';

const LazyComponent: React.FC<{ componentName: string }> = ({ componentName }) => {
    const Component = lazy(() => import(`@/components/${componentName}`));

    return (
        <Suspense fallback={
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <ActivityIndicator size="large" />
            </View>
        }>
            <Component />
        </Suspense>
    );
};

export default LazyComponent;

2. 图片预加载

// src/hooks/useImagePreload.ts
import { useEffect, useState } from 'react';
import { Image } from 'expo-image';

export const useImagePreload = (imageUrls: string[]) => {
    const [loadedCount, setLoadedCount] = useState(0);
    const [isComplete, setIsComplete] = useState(false);

    useEffect(() => {
        const preloadImages = async () => {
            const promises = imageUrls.map(async (url) => {
                try {
                    await Image.prefetch(url);
                    setLoadedCount(prev => prev + 1);
                } catch (error) {
                    console.warn(`Failed to preload image: ${url}`, error);
                }
            });

            await Promise.allSettled(promises);
            setIsComplete(true);
        };

        preloadImages();
    }, [imageUrls]);

    return {
        loadedCount,
        totalCount: imageUrls.length,
        progress: imageUrls.length > 0 ? loadedCount / imageUrls.length : 0,
        isComplete,
    };
};

3. 内存优化

// src/hooks/useMemoryOptimization.ts
import { useEffect, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native';

export const useMemoryOptimization = () => {
    const appState = useRef(AppState.currentState);

    useEffect(() => {
        const handleAppStateChange = (nextAppState: AppStateStatus) => {
            if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
                // 应用从后台回到前台,清理缓存
                console.log('App has come to the foreground!');
                // 执行内存清理操作
            }

            appState.current = nextAppState;
        };

        const subscription = AppState.addEventListener('change', handleAppStateChange);

        return () => subscription?.remove();
    }, []);
};

最佳实践总结

1. 架构设计原则

  • 模块化设计:按功能模块组织代码结构
  • 关注点分离:UI、业务逻辑、数据管理分离
  • 类型安全:充分利用 TypeScript 类型系统
  • 可测试性:组件和逻辑易于单元测试

2. 状态管理最佳实践

  • 状态分层:全局状态、页面状态、组件状态合理分层
  • 数据持久化:关键数据使用 Zustand persist 中间件
  • 状态更新:使用不可变更新模式
  • 性能优化:避免不必要的重新渲染

3. 路由管理最佳实践

  • 文件路由:利用 Expo Router 的文件路由特性
  • 路由守卫:在根布局中实现路由守卫逻辑
  • 参数传递:使用类型安全的参数传递
  • 深度链接:支持应用内深度链接

4. 性能优化要点

  • 懒加载:组件和资源按需加载
  • 缓存策略:合理使用缓存减少网络请求
  • 内存管理:及时清理不需要的资源
  • 渲染优化:使用 React.memo 和 useMemo

5. 错误处理策略

  • 全局错误处理:统一的错误处理和上报
  • 用户友好提示:错误信息对用户友好
  • 降级方案:关键功能异常时的降级处理
  • 监控告警:完善的错误监控和告警机制

结语

通过本文的深入探讨,我们构建了一套基于 Expo Router 和 Zustand 的现代化 React Native 应用架构。这套架构不仅解决了当前项目的实际需求,还具备了良好的扩展性和维护性。

在实际应用中,我们需要根据具体的业务场景和团队情况,灵活调整和优化各个模块。同时,随着 React Native 生态的不断发展,我们也需要持续关注新技术的发展,及时升级和优化我们的架构。

希望本文能够为正在构建 React Native 应用的开发者提供有价值的参考。在移动应用开发的道路上,架构设计、性能优化、用户体验的平衡永远是一个需要持续探索的话题。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐