# 第三章:状态管理架构设计 - 从 Zustand 到 React Query 的完整实践

在上一章中,我们深入探讨了 Expo Router 的路由架构设计。本章将详细介绍维小美医疗云的状态管理架构,包括 Zustand 全局状态管理、React Query 服务端状态管理、以及两者如何协同工作构建高效的数据流。

3.1 状态管理架构概览

3.1.1 状态分类与职责划分

在医疗应用中,我们需要管理多种类型的状态。我们采用了清晰的状态分类策略:

typescript 复制代码
// 状态分类架构
状态管理架构
├── 客户端状态 (Zustand)
│   ├── 全局应用状态 (AppState)
│   ├── 用户认证状态 (UserState)
│   ├── UI交互状态 (UIState)
│   └── 业务状态 (BusinessState)
└── 服务端状态 (React Query)
    ├── 患者数据 (Patient Data)
    ├── 预约数据 (Appointment Data)
    ├── 病历数据 (Medical Records)
    └── 统计数据 (Statistics Data)

3.1.2 状态管理原则

我们遵循以下核心原则来设计状态管理架构:

  1. 单一数据源:每种状态只有一个权威来源
  2. 状态不可变性:状态更新必须通过纯函数进行
  3. 状态持久化:关键状态需要持久化到本地存储
  4. 类型安全:所有状态都有完整的 TypeScript 类型定义
  5. 性能优化:合理使用缓存和订阅优化

3.2 Zustand 全局状态管理实践

3.2.1 应用状态管理 (AppState)

应用状态管理全局的应用配置和生命周期状态:

typescript 复制代码
// stores/global/appStore.ts
export interface AppState {
  // 应用设置
  theme: 'light' | 'dark';
  language: 'zh-CN' | 'en-US';
  version: string;

  // 网络状态
  isOnline: boolean;

  // 应用状态
  isLoading: boolean;
  error: string | null;

  // 启动/登录相关
  isBooted: boolean; // App 是否已初始化
  splashLoading: boolean; // 启动页加载中
  hasTriedAutoLogin: boolean; // 是否尝试过自动登录
  lastLoginTime: number | null; // 上次登录时间戳
  lastUserId: string | null; // 上次登录用户ID

  // PWA 相关
  isInstalled: boolean;
  updateAvailable: boolean;

  // Actions
  setTheme: (theme: 'light' | 'dark') => void;
  setLanguage: (language: 'zh-CN' | 'en-US') => void;
  setOnlineStatus: (isOnline: boolean) => void;
  setLoading: (isLoading: boolean) => void;
  setError: (error: string | null) => void;
  setInstalled: (isInstalled: boolean) => void;
  setUpdateAvailable: (updateAvailable: boolean) => void;
  clearError: () => void;
  reset: () => void;

  // 启动/登录相关 actions
  setBooted: (isBooted: boolean) => void;
  setSplashLoading: (loading: boolean) => void;
  setHasTriedAutoLogin: (tried: boolean) => void;
  setLastLoginTime: (time: number | null) => void;
  setLastUserId: (userId: string | null) => void;
}

const initialState = {
  theme: 'light' as const,
  language: 'zh-CN' as const,
  version: '1.0.0',
  isOnline: true,
  isLoading: false,
  error: null,
  isInstalled: false,
  updateAvailable: false,
  isBooted: false,
  splashLoading: true,
  hasTriedAutoLogin: false,
  lastLoginTime: null,
  lastUserId: null,
};

export const useAppStore = create<AppState>()(
  persist(
    (set, get) => ({
      ...initialState,

      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
      setOnlineStatus: (isOnline) => set({ isOnline }),
      setLoading: (isLoading) => set({ isLoading }),
      setError: (error) => set({ error }),
      setInstalled: (isInstalled) => set({ isInstalled }),
      setUpdateAvailable: (updateAvailable) => set({ updateAvailable }),
      clearError: () => set({ error: null }),
      reset: () => set(initialState),
      setBooted: (isBooted) => set({ isBooted }),
      setSplashLoading: (loading) => set({ splashLoading: loading }),
      setHasTriedAutoLogin: (tried) => set({ hasTriedAutoLogin: tried }),
      setLastLoginTime: (time) => set({ lastLoginTime: time }),
      setLastUserId: (userId) => set({ lastUserId: userId }),
    }),
    {
      name: 'app-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 只持久化必要的状态
        theme: state.theme,
        language: state.language,
        isInstalled: state.isInstalled,
        isBooted: state.isBooted,
        splashLoading: state.splashLoading,
        hasTriedAutoLogin: state.hasTriedAutoLogin,
        lastLoginTime: state.lastLoginTime,
        lastUserId: state.lastUserId,
      }),
    }
  )
);

3.2.2 用户认证状态管理 (UserState)

用户认证状态管理用户的登录状态和权限信息:

typescript 复制代码
// stores/auth/userStore.ts
export interface User {
  id: string;
  username: string;
  email?: string;
  phone?: string;
  avatar?: string;
  nickname?: string;
  department?: string;
  role?: string;
  permissions?: string[];
  affiliated_org: number;
  org_type_code?: string;
}

export interface UserState {
  // 用户信息
  user: User | null;
  isAuthenticated: boolean;
  token: string | null;
  refreshToken: string | null;

  // 登录状态
  isLoggingIn: boolean;
  loginError: string | null;

  // Actions
  setUser: (user: User) => void;
  setTokens: (token: string, refreshToken?: string) => void;
  login: (username: string, password: string) => Promise<DoLoginUsingPostResponse>;
  logout: () => void;
  updateUser: (updates: Partial<User>) => void;
  clearError: () => void;
  setLoggingIn: (isLoggingIn: boolean) => void;
  restoreUserFromStorage: () => Promise<void>;
}

export const useUserStore = create<UserState>()(
  persist(
    (set, get) => ({
      user: null,
      isAuthenticated: false,
      token: null,
      refreshToken: null,
      isLoggingIn: false,
      loginError: null,

      setUser: (user) => {
        set({ user, isAuthenticated: !!user });
        // 同时保存到独立存储
        if (user) {
          setUserInStorage(user as unknown as UserState);
        }
      },

      setTokens: (token, refreshToken) => set({
        token,
        refreshToken,
        isAuthenticated: !!token
      }),

      login: async (username, password) => {
        set({ isLoggingIn: true, loginError: null });
        try {
          const res: DoLoginUsingPostResponse = await doLoginUsingPost({
            body: { username, password }
          });
          if (res.status_code === 0 && res.data) {
            console.log('✅ userStore.login: 登录成功,开始保存 token...', {
              hasAccessToken: !!res.data.access_token,
              hasRefreshToken: !!res.data.refresh_token,
              userId: res.data.userid
            });

            await setAccessToken(res.data.access_token);
            await setRefreshToken(res.data.refresh_token);
            console.log('✅ userStore.login: token 保存到安全存储完成');

            const userPayload: User = {
              id: res.data.userid,
              username: res.data.username,
              nickname: res.data.username,
              affiliated_org: res.data.affiliated_org as number,
              org_type_code: res.data.org_type_code,
              department: '',
              role: '',
              permissions: ['image:view', 'image:medical'], // 实际应该从后端获取
            };

            await setUserInStorage(userPayload as unknown as UserState);
            console.log('✅ userStore.login: 用户信息保存到通用存储完成');

            set({
              token: res.data.access_token,
              refreshToken: res.data.refresh_token,
              user: userPayload,
              isAuthenticated: true,
              isLoggingIn: false,
              loginError: null
            });
            console.log('✅ userStore.login: 用户状态设置完成');
            return res;
          } else {
            throw res.status_msg || '登录失败';
          }
        } catch (error: any) {
          set({
            loginError: error,
            isLoggingIn: false,
            isAuthenticated: false
          });
          throw error;
        }
      },

      logout: async () => {
        console.log('🚪 userStore.logout: 开始登出流程...');
        const res = await doLogoutUsingGet();
        if( !(res?.status_code === 0) ) {
          Alert.alert('登出失败', '登出失败,请联系管理员');
          console.log('🚪 userStore.logout: 登出失败');
          return
        }

        // 清除安全存储
        console.log('🗑️ userStore.logout: 清除安全存储...');
        await removeAccessToken();
        await removeRefreshToken();
        await removeUser();

        // 清除 store 状态
        console.log('🗑️ userStore.logout: 清除用户状态...');
        set({
          user: null,
          token: null,
          refreshToken: null,
          isAuthenticated: false,
          loginError: null
        });

        console.log('✅ userStore.logout: 登出完成');
      },

      updateUser: (updates) => {
        const currentUser = get().user;
        if (currentUser) {
          set({ user: { ...currentUser, ...updates } });
        }
      },

      clearError: () => set({ loginError: null }),

      setLoggingIn: (isLoggingIn) => set({ isLoggingIn }),

      // 从存储中恢复用户信息
      restoreUserFromStorage: async () => {
        try {
          console.log('🔄 开始从存储恢复用户信息...');
          const storedUser = await getUser();
          if (storedUser && storedUser.user) {
            console.log('✅ 成功恢复用户信息:', storedUser.user.username);
            set({
              user: storedUser.user as User,
              isAuthenticated: true
            });
          } else {
            console.warn('⚠️ 存储中没有找到用户信息');
          }
        } catch (error) {
          console.error('❌ 恢复用户信息失败:', error);
        }
      },
    }),
    {
      name: 'user-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 持久化认证状态和用户信息,token由SecureStore管理
        isAuthenticated: state.isAuthenticated,
        user: state.user,
      }),
    }
  )
);

3.2.3 UI 状态管理 (UIState)

UI 状态管理全局的界面交互状态:

typescript 复制代码
// stores/global/uiStore.ts
export interface UIState {
  // 模态框状态,控制各类弹窗的显示与隐藏
  modals: {
    departmentSelector: boolean; // 部门选择弹窗
    errorModal: boolean;         // 错误提示弹窗
    wechatModal: boolean;        // 微信相关弹窗
    confirmModal: boolean;       // 通用确认弹窗
  };

  // 加载状态,控制全局及各功能的 loading
  loading: {
    global: boolean; // 全局 loading
    search: boolean; // 搜索 loading
    login: boolean;  // 登录 loading
    stats: boolean;  // 统计 loading
  };

  // 搜索相关状态
  searchValue: string;      // 当前搜索框内容
  searchHistory: string[];  // 搜索历史记录

  // 选择器状态
  selectedDepartment: string; // 当前选中的部门

  // 通知状态,存储所有通知
  notifications: Notification[];

  // 页面状态,可存储任意页面相关的临时状态
  pageStates: {
    [key: string]: any;
  };

  // ========== Actions ========== //
  // 打开指定弹窗
  openModal: (modalName: keyof UIState['modals']) => void;
  // 关闭指定弹窗
  closeModal: (modalName: keyof UIState['modals']) => void;
  // 关闭所有弹窗
  closeAllModals: () => void;

  // 设置 loading 状态
  setLoading: (key: keyof UIState['loading'], value: boolean) => void;
  // 设置全局 loading
  setGlobalLoading: (loading: boolean) => void;

  // 设置搜索框内容
  setSearchValue: (value: string) => void;
  // 添加搜索历史
  addSearchHistory: (value: string) => void;
  // 清空搜索历史
  clearSearchHistory: () => void;

  // 设置当前选中部门
  setSelectedDepartment: (department: string) => void;

  // 添加通知
  addNotification: (notification: Omit<Notification, 'id' | 'timestamp'>) => void;
  // 移除通知
  removeNotification: (id: string) => void;
  // 清空所有通知
  clearNotifications: () => void;

  // 设置页面状态
  setPageState: (page: string, state: any) => void;
  // 获取页面状态
  getPageState: (page: string) => any;
  // 清除页面状态
  clearPageState: (page: string) => void;
}

// UI 状态 Store 实现
export const useUIStore = create<UIState>((set, get) => ({
  ...initialState,

  // 打开指定弹窗
  openModal: (modalName) =>
    set((state) => ({
      modals: { ...state.modals, [modalName]: true }
    })),

  // 关闭指定弹窗
  closeModal: (modalName) =>
    set((state) => ({
      modals: { ...state.modals, [modalName]: false }
    })),

  // 关闭所有弹窗
  closeAllModals: () =>
    set((state) => ({
      modals: Object.keys(state.modals).reduce((acc, key) => {
        acc[key as keyof typeof state.modals] = false;
        return acc;
      }, {} as typeof state.modals)
    })),

  // 设置 loading 状态
  setLoading: (key, value) =>
    set((state) => ({
      loading: { ...state.loading, [key]: value }
    })),

  // 设置全局 loading
  setGlobalLoading: (loading) =>
    set((state) => ({
      loading: { ...state.loading, global: loading }
    })),

  // 设置搜索框内容
  setSearchValue: (value) => set({ searchValue: value }),

  // 添加搜索历史(去重,最多10条)
  addSearchHistory: (value) => {
    const { searchHistory } = get();
    const newHistory = [value, ...searchHistory.filter(item => item !== value)].slice(0, 10);
    set({ searchHistory: newHistory });
  },

  // 清空搜索历史
  clearSearchHistory: () => set({ searchHistory: [] }),

  // 设置当前选中部门
  setSelectedDepartment: (department) => set({ selectedDepartment: department }),

  // 添加通知(自动生成唯一 id,支持自动移除)
  addNotification: (notification) => {
    const id = generateNotificationId();
    const newNotification: Notification = {
      ...notification,
      id,
      timestamp: dayjs().toISOString(),
    };

    set((state) => ({
      notifications: [...state.notifications, newNotification]
    }));

    // 自动移除通知(可选)
    if (notification.autoRemove !== false) {
      setTimeout(() => {
        get().removeNotification(id);
      }, notification.duration || 5000);
    }
  },

  // 移除通知
  removeNotification: (id) =>
    set((state) => ({
      notifications: state.notifications.filter(n => n.id !== id)
    })),

  // 清空所有通知
  clearNotifications: () => set({ notifications: [] }),

  // 设置页面状态
  setPageState: (page, state) =>
    set((currentState) => ({
      pageStates: { ...currentState.pageStates, [page]: state }
    })),

  // 获取页面状态
  getPageState: (page) => {
    const { pageStates } = get();
    return pageStates[page];
  },

  // 清除页面状态
  clearPageState: (page) =>
    set((state) => {
      const newPageStates = { ...state.pageStates };
      delete newPageStates[page];
      return { pageStates: newPageStates };
    }),
}));

3.2.4 业务状态管理

患者选择状态管理

在医疗应用中,患者选择是一个复杂的跨页面流程:

typescript 复制代码
// stores/patientSelectionStore.ts
export interface SelectedPatient {
  id: string;
  name: string;
  gender: string;
  age: string | number;
  phone: string;
  recordId?: string;
  consultItem?: string;
  source?: string;
}

export type SourcePageType = 'add-appointment' | 'edit-appointment' | 'patient-detail' | null;

interface PatientSelectionState {
  // 选中的患者信息
  selectedPatient: SelectedPatient | null;

  // 来源页面标识
  sourcePageType: SourcePageType;

  // 保存的表单数据(用于新建预约页面)
  savedFormData: Record<string, any> | null;

  // 编辑预约的ID(用于编辑页面)
  editReservationId: string | null;

  // 返回路径(用于从患者搜索返回)
  returnPath: string | null;

  // 是否正在处理患者选择(防止重复处理)
  isProcessingPatientSelection: boolean;

  // 最后处理的患者ID(用于检测患者变化)
  lastProcessedPatientId: string | null;

  // Actions
  setSelectedPatient: (patient: SelectedPatient | null) => void;
  setSourcePage: (pageType: SourcePageType) => void;
  setSavedFormData: (formData: Record<string, any> | null) => void;
  setEditReservationId: (id: string | null) => void;
  setReturnPath: (path: string | null) => void;
  setProcessingState: (isProcessing: boolean) => void;
  setLastProcessedPatientId: (id: string | null) => void;

  // 清除所有状态
  clearAll: () => void;

  // 获取并清除患者信息(一次性使用)
  consumeSelectedPatient: () => SelectedPatient | null;

  // 设置患者信息并保存表单数据(用于患者搜索场景)
  setPatientWithFormData: (
    patient: SelectedPatient,
    formData?: Record<string, any>,
    sourceType?: SourcePageType
  ) => void;

  // 准备患者搜索(保存当前状态)
  preparePatientSearch: (
    formData: Record<string, any>,
    sourceType: SourcePageType,
    editId?: string
  ) => void;

  shouldProcessPatient: (patientId: string) => boolean;
}

export const usePatientSelectionStore = create<PatientSelectionState>((set, get) => ({
  selectedPatient: null,
  sourcePageType: null,
  savedFormData: null,
  editReservationId: null,
  returnPath: null,
  isProcessingPatientSelection: false,
  lastProcessedPatientId: null,

  setSelectedPatient: (patient) => set({ selectedPatient: patient }),
  setSourcePage: (pageType) => set({ sourcePageType: pageType }),
  setSavedFormData: (formData) => set({ savedFormData: formData }),
  setEditReservationId: (id) => set({ editReservationId: id }),
  setReturnPath: (path) => set({ returnPath: path }),
  setProcessingState: (isProcessing) => set({ isProcessingPatientSelection: isProcessing }),
  setLastProcessedPatientId: (id) => set({ lastProcessedPatientId: id }),

  clearAll: () => set({
    selectedPatient: null,
    sourcePageType: null,
    savedFormData: null,
    editReservationId: null,
    returnPath: null,
    isProcessingPatientSelection: false,
    lastProcessedPatientId: null,
  }),

  consumeSelectedPatient: () => {
    const { selectedPatient } = get();
    // 获取后立即清除,确保只使用一次
    set({
      selectedPatient: null,
      isProcessingPatientSelection: false,
      lastProcessedPatientId: null
    });
    return selectedPatient;
  },

  setPatientWithFormData: (patient, formData, sourceType) => set({
    selectedPatient: patient,
    savedFormData: formData || null,
    sourcePageType: sourceType || null,
    isProcessingPatientSelection: false,
    lastProcessedPatientId: patient.id,
  }),

  preparePatientSearch: (formData, sourceType, editId) => set({
    savedFormData: formData,
    sourcePageType: sourceType,
    editReservationId: editId || null,
    isProcessingPatientSelection: false,
  }),

  shouldProcessPatient: (patientId) => {
    const { isProcessingPatientSelection, lastProcessedPatientId } = get();
    return !isProcessingPatientSelection && lastProcessedPatientId !== patientId;
  },
}));
工作统计状态管理
typescript 复制代码
// stores/business/workStatsStore.ts
export interface WorkStatistic {
  indicator: StatisticsIndicator;
  dimension: StatisticsDimension;
  value: number;
  unit?: string;
  lastUpdated: string;
}

export interface WorkStatsState {
  statistics: WorkStatistic[];
  isLoading: boolean;
  error: string | null;
  lastFetchTime: string | null;

  setStatistics: (stats: WorkStatistic[]) => void;
  addStatistic: (stat: WorkStatistic) => void;
  updateStatistic: (indicator: StatisticsIndicator, dimension: StatisticsDimension, value: number) => void;
  setLoading: (loading: boolean) => void;
  setError: (error: string | null) => void;
  clearError: () => void;
  getStatistic: (indicator: StatisticsIndicator, dimension: StatisticsDimension) => WorkStatistic | undefined;
  clearStatistics: () => void;
}

// 创建工作统计 store
export const useWorkStatsStore = create<WorkStatsState>((set, get) => ({
  statistics: [],
  isLoading: false,
  error: null,
  lastFetchTime: null,

  setStatistics: (stats) => set({
    statistics: stats,
    lastFetchTime: dayjs().toISOString(),
    error: null
  }),

  addStatistic: (stat) => set((state) => {
    const existingIndex = state.statistics.findIndex(
      s => s.indicator === stat.indicator && s.dimension === stat.dimension
    );

    let newStatistics;
    if (existingIndex >= 0) {
      // 更新现有统计
      newStatistics = [...state.statistics];
      newStatistics[existingIndex] = { ...stat, lastUpdated: dayjs().toISOString() };
    } else {
      // 添加新统计
      newStatistics = [...state.statistics, { ...stat, lastUpdated: dayjs().toISOString() }];
    }

    return {
      statistics: newStatistics,
      lastFetchTime: dayjs().toISOString(),
      error: null
    };
  }),

  updateStatistic: (indicator, dimension, value) => set((state) => {
    const newStatistics = state.statistics.map(stat =>
      stat.indicator === indicator && stat.dimension === dimension
        ? { ...stat, value, lastUpdated: dayjs().toISOString() }
        : stat
    );

    return {
      statistics: newStatistics,
      lastFetchTime: dayjs().toISOString()
    };
  }),

  setLoading: (loading) => set({ isLoading: loading }),
  setError: (error) => set({ error, isLoading: false }),
  clearError: () => set({ error: null }),

  getStatistic: (indicator, dimension) => {
    const state = get();
    return state.statistics.find(
      stat => stat.indicator === indicator && stat.dimension === dimension
    );
  },

  clearStatistics: () => set({
    statistics: [],
    error: null,
    lastFetchTime: null
  }),
}));

3.2.5 状态持久化策略

我们使用 Zustand 的 persist 中间件来实现状态持久化:

typescript 复制代码
// 持久化配置示例
export const useAppStore = create<AppState>()(
  persist(
    (set, get) => ({
      // store 实现
    }),
    {
      name: 'app-storage', // 存储键名
      storage: createJSONStorage(() => AsyncStorage), // 使用 AsyncStorage
      partialize: (state) => ({
        // 只持久化必要的状态,避免存储敏感信息
        theme: state.theme,
        language: state.language,
        isInstalled: state.isInstalled,
        isBooted: state.isBooted,
        splashLoading: state.splashLoading,
        hasTriedAutoLogin: state.hasTriedAutoLogin,
        lastLoginTime: state.lastLoginTime,
        lastUserId: state.lastUserId,
      }),
    }
  )
);

3.2.6 状态订阅与优化

在组件中使用状态时,我们采用精确订阅来优化性能:

typescript 复制代码
// 精确订阅示例
const PatientListScreen = () => {
  // 只订阅需要的状态
  const { modals, openModal, closeModal } = useUIStore();
  const { user } = useUserStore();
  const { theme } = useAppStore();

  // 或者使用选择器函数
  const isGlobalLoading = useUIStore(state => state.loading.global);
  const selectedDepartment = useUIStore(state => state.selectedDepartment);

  return (
    // 组件实现
  );
};

3.3 React Query 服务端状态管理

3.3.1 查询键管理策略

我们建立了统一的查询键管理策略:

typescript 复制代码
// servers/case/caseService.ts
export const caseKeys = {
  all: ['case'] as const,
  lists: () => [...caseKeys.all, 'list'] as const,
  list: (customerId: number) => [...caseKeys.lists(), customerId] as const,
  details: () => [...caseKeys.all, 'detail'] as const,
  detail: (id: number) => [...caseKeys.details(), id] as const,
  templates: () => [...caseKeys.all, 'templates'] as const,
  template: (orgId: number) => [...caseKeys.templates(), orgId] as const,
};

// servers/billing/billingService.ts
export const billingKeys = {
  all: ['billing'] as const,
  lists: () => [...billingKeys.all, 'list'] as const,
  list: (customerId: number) => [...billingKeys.lists(), customerId] as const,
  listWithPage: (customerId: number, pageNum: number) =>
    [...billingKeys.list(customerId), pageNum] as const,
  details: () => [...billingKeys.all, 'detail'] as const,
  detail: (id: number) => [...billingKeys.details(), id] as const,
  summary: (customerId: number) => [...billingKeys.all, 'summary', customerId] as const,
};

3.3.2 基础查询实现

typescript 复制代码
// servers/case/caseReactQuery.ts
/**
 * 获取患者病历列表
 * @param customerId - 患者ID
 * @param loginOrgId - 登录机构ID
 * @returns 患者病历列表查询结果
 */
export function useCustomerEmr(customerId: number, loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.list(customerId),
    queryFn: async () => {
      const response = await getCustomerEmr({
        body: { customerId, loginOrgId },
      });
      return response;
    },
    enabled: !!customerId && customerId > 0 && !!loginOrgId && loginOrgId > 0,
    staleTime: 0, // 5分钟缓存
  });
}

/**
 * 获取病历模板树形结构
 * @param loginOrgId - 登录机构ID
 * @returns 模板树形结构查询结果
 */
export function useEmrItemTree(loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.template(loginOrgId),
    queryFn: async () => {
      const response = await getEmrItemTree({
        body: { loginOrgId },
      });
      return response;
    },
    enabled: !!loginOrgId && loginOrgId > 0,
    staleTime: 5 * 60 * 1000, // 5分钟缓存
  });
}

3.3.3 无限查询实现

对于分页数据,我们使用无限查询来优化用户体验:

typescript 复制代码
// servers/returnVisit/huifang.infinite.ts
/** 回访列表无限查询 - useInfiniteQuery 版本,用于分页加载 */
export function useFollowGetFollowListInfiniteQuery(
  params: {
    customerId: number;
    pageSize?: number;
  },
  options?: {
    enabled?: boolean;
    staleTime?: number;
  }
) {
  const pageSize = params.pageSize || 10;

  return useInfiniteQuery({
    queryKey: ['followUpListInfinite', params.customerId, pageSize],
    queryFn: async ({ pageParam = 0 }) => {
      console.log('🚀 执行回访列表无限查询:', {
        customerId: params.customerId,
        pageNum: pageParam,
        pageSize
      });

      const response = await apis.followGetFollowListUsingPost({
        body: {
          condition: {
            customerId: params.customerId,
          },
          pageNum: pageParam,
          pageSize: pageSize,
        },
      });

      console.log('📥 回访列表无限查询API响应:', response);

      if (response.status_code !== 0) {
        throw new Error(response.status_msg || '获取回访列表失败');
      }

      return response;
    },
    getNextPageParam: (lastPage) => {
      const { pageIndex = 0, pageCount = 1 } = lastPage.data || {};
      // 如果当前页小于总页数-1,返回下一页页码
      return pageIndex < pageCount - 1 ? pageIndex + 1 : undefined;
    },
    getPreviousPageParam: (firstPage) => {
      const { pageIndex = 0 } = firstPage.data || {};
      // 如果当前页大于0,返回上一页页码
      return pageIndex > 0 ? pageIndex - 1 : undefined;
    },
    enabled: options?.enabled !== false && !!params.customerId,
    staleTime: 0,
    gcTime: 10 * 60 * 1000,
    refetchOnWindowFocus: false,
    refetchOnMount: false,
    initialPageParam: 0,
  });
}

// servers/billing/billingInfinite.ts
/**
 * 无限查询 hook - 计费记录列表
 */
export function useBillingListInfinite(params: UseBillingListInfiniteParams) {
  const { customerId, pageSize = 20 } = params;

  return useInfiniteQuery({
    queryKey: [...billingKeys.list(customerId), 'infinite'],
    queryFn: async ({ pageParam = 0 }) => {
      const response = await getBillingList({
        body: {
          customerId,
          pageNum: pageParam,
          pageSize,
        },
      });
      return response;
    },
    getNextPageParam: (lastPage) => {
      const { pageIndex = 0, pageCount = 1 } = lastPage.data || {};
      return pageIndex < pageCount - 1 ? pageIndex + 1 : undefined;
    },
    initialPageParam: 0,
    enabled: !!customerId && customerId > 0,
    staleTime: 0, // 5分钟缓存
  });
}

/**
 * 获取扁平化的计费记录列表
 */
export function getFlatBillingList(data: any): BillingRecord[] {
  return data?.pages?.flatMap((page: any) => page.data?.resultList || []) || [];
}

3.3.4 变更操作实现

typescript 复制代码
// servers/case/caseReactQuery.ts
/**
 * 添加病历信息
 */
export function useAddCureEmrInfoMutation(options?: {
  onSuccess?: (data: AddCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addCureEmrInfo,
    onSuccess: (data, variables) => {
      // 刷新相关查询
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false
      });

      showSuccessAlert('病历信息添加成功');
      options?.onSuccess?.(data);
    },
    onError: (error) => {
      const message = error?.response?.data?.status_msg
        || error?.message
        || '添加病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
  });
}

/**
 * 更新病历信息
 */
export function useUpdateCureEmrInfoMutation(options?: {
  onSuccess?: (data: UpdateCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateCureEmrInfo,
    onSuccess: (data, variables) => {
      // 刷新相关查询
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false
      });

      showSuccessAlert('病历信息更新成功');
      options?.onSuccess?.(data);
    },
    onError: (error) => {
      const message = error?.response?.data?.status_msg
        || error?.message
        || '更新病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
  });
}

这是第三章的第一部分,涵盖了 Zustand 状态管理和 React Query 的基础实现。接下来我将继续编写第二部分,包括缓存策略、错误处理、性能优化等内容。

第三章:状态管理架构设计 - 第二部分

3.4 缓存策略与数据同步

3.4.1 智能缓存管理

我们实现了全局的查询管理器来统一管理缓存策略:

typescript 复制代码
// utils/queryUtils.ts
export class QueryManager {
  private static instance: QueryManager;
  private queryClient: QueryClient | null = null;
  private appStateSubscription: any = null;

  private constructor() {}

  static getInstance(): QueryManager {
    if (!QueryManager.instance) {
      QueryManager.instance = new QueryManager();
    }
    return QueryManager.instance;
  }

  /**
   * 初始化 QueryClient
   */
  init(queryClient: QueryClient) {
    this.queryClient = queryClient;
    this.setupAppStateListener();
  }

  /**
   * 设置应用状态监听,当应用回到前台时刷新数据
   */
  private setupAppStateListener() {
    this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => {
      if (nextAppState === 'active' && this.queryClient) {
        console.log('📱 应用回到前台,刷新数据...');
        this.queryClient.invalidateQueries();
      }
    });
  }

  /**
   * 清除所有缓存
   */
  clearAllCache() {
    if (!this.queryClient) return;
    console.log('🗑️ 清除所有缓存');
    this.queryClient.clear();
  }

  /**
   * 刷新所有查询
   */
  async refreshAllQueries() {
    if (!this.queryClient) return;
    console.log('🔄 刷新所有查询');
    await this.queryClient.invalidateQueries();
  }

  /**
   * 刷新特定的查询
   */
  async refreshQueries(queryKey: string[]) {
    if (!this.queryClient) return;
    console.log('🔄 刷新查询:', queryKey);
    await this.queryClient.invalidateQueries({ queryKey });
  }

  /**
   * 预取数据
   */
  async prefetchQuery(queryKey: string[], queryFn: () => Promise<any>) {
    if (!this.queryClient) return;
    console.log('📥 预取数据:', queryKey);
    await this.queryClient.prefetchQuery({
      queryKey,
      queryFn,
    });
  }

  /**
   * 获取缓存的查询数据
   */
  getQueryData(queryKey: string[]) {
    if (!this.queryClient) return null;
    return this.queryClient.getQueryData(queryKey);
  }

  /**
   * 设置查询数据到缓存
   */
  setQueryData(queryKey: string[], data: any) {
    if (!this.queryClient) return;
    this.queryClient.setQueryData(queryKey, data);
  }

  /**
   * 移除特定查询的缓存
   */
  removeQueries(queryKey: string[]) {
    if (!this.queryClient) return;
    console.log('🗑️ 移除查询缓存:', queryKey);
    this.queryClient.removeQueries({ queryKey });
  }

  /**
   * 获取查询状态
   */
  getQueryState(queryKey: string[]) {
    if (!this.queryClient) return null;
    return this.queryClient.getQueryState(queryKey);
  }

  /**
   * 销毁监听器
   */
  destroy() {
    if (this.appStateSubscription) {
      this.appStateSubscription.remove();
      this.appStateSubscription = null;
    }
  }
}

// 导出单例实例
export const queryManager = QueryManager.getInstance();

/**
 * Hook:在组件中使用全局查询管理器
 */
export function useQueryManager() {
  return {
    clearAllCache: () => queryManager.clearAllCache(),
    refreshAllQueries: () => queryManager.refreshAllQueries(),
    refreshQueries: (queryKey: string[]) => queryManager.refreshQueries(queryKey),
    prefetchQuery: (queryKey: string[], queryFn: () => Promise<any>) =>
      queryManager.prefetchQuery(queryKey, queryFn),
    getQueryData: (queryKey: string[]) => queryManager.getQueryData(queryKey),
    setQueryData: (queryKey: string[], data: any) => queryManager.setQueryData(queryKey, data),
    removeQueries: (queryKey: string[]) => queryManager.removeQueries(queryKey),
    getQueryState: (queryKey: string[]) => queryManager.getQueryState(queryKey),
  };
}

3.4.2 缓存失效策略

我们实现了精确的缓存失效策略,确保数据的一致性:

typescript 复制代码
// 在数据变更后刷新相关查询
export function useAddCureEmrInfoMutation(options?: {
  onSuccess?: (data: AddCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addCureEmrInfo,
    onSuccess: (data, variables) => {
      // 刷新该患者的所有病历查询
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false // 不精确匹配,会刷新所有以此开头的查询键
      });

      // 同时刷新无限查询
      queryClient.invalidateQueries({
        queryKey: ['followUpListInfinite', variables.body.customerId],
        exact: false
      });

      showSuccessAlert('病历信息添加成功');
      options?.onSuccess?.(data);
    },
    onError: (error) => {
      const message = error?.response?.data?.status_msg
        || error?.message
        || '添加病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
  });
}

// 批量刷新相关数据
export function useRefreshImageRecordsData() {
  const queryClient = useQueryClient();

  return useCallback((customerId: number) => {
    // 刷新影像记录列表
    queryClient.invalidateQueries({
      queryKey: imageRecordsKeys.list(customerId),
      exact: false
    });

    // 刷新预约记录列表
    queryClient.invalidateQueries({
      queryKey: imageRecordsKeys.reservations(customerId),
      exact: false
    });

    console.log('🔄 已刷新影像记录相关数据');
  }, [queryClient]);
}

3.4.3 乐观更新实现

对于用户操作,我们实现了乐观更新来提升用户体验:

typescript 复制代码
// 乐观更新示例
export function useUpdateCureEmrInfoMutation(options?: {
  onSuccess?: (data: UpdateCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateCureEmrInfo,
    onMutate: async (variables) => {
      // 取消正在进行的查询,避免冲突
      await queryClient.cancelQueries({
        queryKey: caseKeys.list(variables.body.customerId)
      });

      // 保存当前数据
      const previousData = queryClient.getQueryData(
        caseKeys.list(variables.body.customerId)
      );

      // 乐观更新
      queryClient.setQueryData(
        caseKeys.list(variables.body.customerId),
        (old: any) => {
          if (!old) return old;

          // 更新列表中的对应项
          return {
            ...old,
            data: {
              ...old.data,
              resultList: old.data.resultList.map((item: any) =>
                item.id === variables.body.id
                  ? { ...item, ...variables.body }
                  : item
              )
            }
          };
        }
      );

      return { previousData };
    },
    onError: (error, variables, context) => {
      // 回滚到之前的数据
      if (context?.previousData) {
        queryClient.setQueryData(
          caseKeys.list(variables.body.customerId),
          context.previousData
        );
      }

      const message = error?.response?.data?.status_msg
        || error?.message
        || '更新病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
    onSuccess: (data, variables) => {
      // 刷新数据确保一致性
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false
      });

      showSuccessAlert('病历信息更新成功');
      options?.onSuccess?.(data);
    },
  });
}

3.5 错误处理与重试机制

3.5.1 统一错误处理

我们实现了统一的错误处理机制:

typescript 复制代码
// utils/request.ts
// 响应拦截器
request.interceptors.response.use(
  (response) => {
    refreshFailCount = 0; // 成功时重置
    // 直接返回业务数据
    return response.data;
  },
  async (error: AxiosError) => {
    const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean };
    const status = error.response?.status;

    // 统一处理错误信息
    const getErrorMessage = (err: AxiosError): string => {
      if (err.response?.data) {
        const data = err.response.data as any;
        return data.status_msg || data.message || JSON.stringify(data);
      }
      return err.message || '请求发生未知错误';
    };

    const errorMessage = getErrorMessage(error);

    // 401 处理:自动刷新 token
    if (status === 401 && !originalRequest._retry && refreshFailCount < MAX_REFRESH_ATTEMPTS) {
      if (originalRequest.url?.includes('/api/do_refresh_token')) {
        // 如果是刷新token的请求失败,直接处理
        await AuthManager.handleExpired('Token刷新失败,请重新登录');
        return Promise.reject(new ApiError('Token刷新失败', 401));
      }

      isRefreshing = true;
      refreshFailCount++;
      try {
        const refreshToken = await getRefreshToken();
        if (!refreshToken) {
          throw new ApiError('未找到刷新令牌,请重新登录', 401);
        }

        // 调用刷新函数
        const response = await doRefreshTokenUsingPost(refreshToken);

        if (response && response.status_code === 0 && response.data) {
          const { access_token, refresh_token } = response.data;
          await setAccessToken(access_token);
          await setRefreshToken(refresh_token);

          // 刷新token成功后,尝试恢复用户状态
          try {
            const { ensureUserState } = await import('@/utils/auth/userStateManager');
            await ensureUserState();
            console.log('✅ Token刷新后用户状态已恢复');
          } catch (error) {
            console.warn('⚠️ Token刷新后用户状态恢复失败:', error);
          }

          retryQueue.forEach((cb) => cb(access_token));
          retryQueue = [];

          originalRequest.headers = { ...originalRequest.headers, Authorization: `Bearer ${access_token}` };
          return request(originalRequest);
        } else {
          throw new ApiError(response?.status_msg || '刷新token失败', 401);
        }
      } catch (refreshError: any) {
        retryQueue.forEach((cb) => cb(''));
        retryQueue = [];

        if (refreshFailCount >= MAX_REFRESH_ATTEMPTS) {
          await AuthManager.handleExpired('多次刷新token失败,请重新登录');
        }
        return Promise.reject(new ApiError(refreshError.message || '刷新token时出错', 401));
      } finally {
        isRefreshing = false;
      }
    }

    // 处理其他已知错误
    if (isAuthError(errorMessage)) {
      await AuthManager.handleExpired(errorMessage);
    } else {
      AuthManager.handleError(errorMessage);
    }

    // 最终抛出统一格式的错误
    return Promise.reject(new ApiError(errorMessage, status));
  }
);

3.5.2 认证错误处理

typescript 复制代码
// utils/auth.ts
/**
 * 认证管理器
 *
 * 处理token过期、自动跳转登录页、清除用户状态等认证相关的全局操作
 * 支持防重复处理,避免并发请求导致的重复通知和操作
 */

import { useUIStore, useUserStore } from '@/stores';
import { router } from 'expo-router';

// 防重复处理的状态管理
let isHandlingTokenExpired = false;
let lastAuthErrorTime = 0;
let lastTokenExpiredTime = 0;
let lastErrorMessage = '';

// 防重复时间窗口(毫秒)
const AUTH_ERROR_DEBOUNCE_TIME = 3000; // 3秒内相同错误只显示一次
const TOKEN_EXPIRED_DEBOUNCE_TIME = 5000; // 5秒内只处理一次token过期

/**
 * 认证错误处理器(带防重复)
 * @param message 错误信息
 */
export function handleAuthError(message: string) {
  const now = Date.now();

  // 防重复:相同错误信息在时间窗口内只显示一次
  if (message === lastErrorMessage && now - lastAuthErrorTime < AUTH_ERROR_DEBOUNCE_TIME) {
    console.log('跳过重复的认证错误通知:', message);
    return;
  }

  lastAuthErrorTime = now;
  lastErrorMessage = message;

  const { addNotification } = useUIStore.getState();

  // 显示错误通知
  addNotification({
    type: 'error',
    title: '请求失败',
    message: message || '网络请求出现错误',
  });

  console.log('显示认证错误通知:', message);
}

/**
 * Token过期处理器(带防重复)
 * @param reason 过期原因
 */
export async function handleTokenExpired(reason?: string) {
  const now = Date.now();

  // 防重复:在时间窗口内只处理一次token过期
  if (isHandlingTokenExpired || (now - lastTokenExpiredTime < TOKEN_EXPIRED_DEBOUNCE_TIME)) {
    console.log('跳过重复的token过期处理:', reason);
    return;
  }

  isHandlingTokenExpired = true;
  lastTokenExpiredTime = now;

  try {
    console.log('🚪 开始处理token过期:', reason);

    // 清除用户状态
    const { logout } = useUserStore.getState();
    await logout();

    // 清除所有查询缓存
    const { queryManager } = await import('@/utils/queryUtils');
    queryManager.clearAllCache();

    // 跳转到登录页
    router.replace('/login');

    // 显示过期通知
    const { addNotification } = useUIStore.getState();
    addNotification({
      type: 'warning',
      title: '登录已过期',
      message: reason || '您的登录状态已过期,请重新登录',
      duration: 5000,
    });

    console.log('✅ Token过期处理完成');
  } catch (error) {
    console.error('❌ Token过期处理失败:', error);
  } finally {
    isHandlingTokenExpired = false;
  }
}

/**
 * 认证管理器类
 */
export class AuthManager {
  static async handleExpired(reason?: string) {
    await handleTokenExpired(reason);
  }

  static handleError(message: string) {
    handleAuthError(message);
  }
}

3.5.3 React Query 错误处理

typescript 复制代码
// 在 React Query 中处理错误
export function useCustomerEmr(customerId: number, loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.list(customerId),
    queryFn: async () => {
      const response = await getCustomerEmr({
        body: { customerId, loginOrgId },
      });
      return response;
    },
    enabled: !!customerId && customerId > 0 && !!loginOrgId && loginOrgId > 0,
    staleTime: 0,
    retry: (failureCount, error) => {
      // 对于认证错误,不重试
      if (error?.status === 401) {
        return false;
      }
      // 对于网络错误,最多重试3次
      return failureCount < 3;
    },
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
    onError: (error) => {
      console.error('获取患者病历失败:', error);
      // 错误处理由全局拦截器处理
    },
  });
}

3.6 性能优化策略

3.6.1 查询优化

我们通过多种策略优化查询性能:

typescript 复制代码
// 1. 启用查询的条件控制
export function useCustomerEmr(customerId: number, loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.list(customerId),
    queryFn: async () => {
      const response = await getCustomerEmr({
        body: { customerId, loginOrgId },
      });
      return response;
    },
    enabled: !!customerId && customerId > 0 && !!loginOrgId && loginOrgId > 0,
    staleTime: 5 * 60 * 1000, // 5分钟内数据保持新鲜
    gcTime: 10 * 60 * 1000, // 10分钟后垃圾回收
    refetchOnWindowFocus: false, // 窗口聚焦时不自动刷新
    refetchOnMount: false, // 组件挂载时不自动刷新
  });
}

// 2. 预取数据
export function usePrefetchPatientData() {
  const queryClient = useQueryClient();

  return useCallback((customerId: number, loginOrgId: number) => {
    // 预取患者病历数据
    queryClient.prefetchQuery({
      queryKey: caseKeys.list(customerId),
      queryFn: () => getCustomerEmr({ body: { customerId, loginOrgId } }),
      staleTime: 5 * 60 * 1000,
    });

    // 预取影像记录数据
    queryClient.prefetchQuery({
      queryKey: imageRecordsKeys.list(customerId),
      queryFn: () => getImageRecordsList({ body: { customerId } }),
      staleTime: 5 * 60 * 1000,
    });
  }, [queryClient]);
}

// 3. 数据转换优化
export function useImageRecordsData(customerId: number) {
  const { data, isLoading, error, refetch } = useImageRecordsList(customerId);

  // 使用 useMemo 优化数据转换
  const processedData = useMemo(() => {
    if (!data?.data?.resultList) return [];

    return data.data.resultList.map((record: any) => ({
      ...record,
      // 处理图片URL
      imageUrl: record.imageUrl ? `${API_BASE_URL}${record.imageUrl}` : null,
      // 格式化时间
      createTime: dayjs(record.createTime).format('YYYY-MM-DD HH:mm'),
      // 计算文件大小
      fileSize: record.fileSize ? formatFileSize(record.fileSize) : '未知',
    }));
  }, [data]);

  return {
    data: processedData,
    isLoading,
    error,
    refetch,
  };
}

3.6.2 状态订阅优化

typescript 复制代码
// 精确订阅优化
const PatientDetailScreen = () => {
  // 只订阅需要的状态,避免不必要的重渲染
  const { user } = useUserStore(state => ({ user: state.user }));
  const { modals } = useUIStore(state => ({ modals: state.modals }));

  // 使用选择器函数进行精确订阅
  const isGlobalLoading = useUIStore(state => state.loading.global);
  const selectedDepartment = useUIStore(state => state.selectedDepartment);

  // 使用 useCallback 优化函数引用
  const handleEdit = useCallback(() => {
    router.push({
      pathname: '/patient/edit/[id]',
      params: { id: patientId }
    });
  }, [patientId]);

  return (
    // 组件实现
  );
};

// 使用 React.memo 优化组件渲染
const PatientCard = React.memo(({ patient, onPress }: PatientCardProps) => {
  return (
    <TouchableOpacity onPress={onPress}>
      {/* 患者卡片内容 */}
    </TouchableOpacity>
  );
});

3.6.3 内存管理

typescript 复制代码
// 组件卸载时清理状态
const PatientListScreen = () => {
  const { clearPageState } = useUIStore();

  useEffect(() => {
    return () => {
      // 组件卸载时清理页面状态
      clearPageState('patientList');
    };
  }, [clearPageState]);

  return (
    // 组件实现
  );
};

// 定期清理过期缓存
export class CacheManager {
  private static instance: CacheManager;
  private cleanupInterval: NodeJS.Timeout | null = null;

  static getInstance(): CacheManager {
    if (!CacheManager.instance) {
      CacheManager.instance = new CacheManager();
    }
    return CacheManager.instance;
  }

  startPeriodicCleanup() {
    // 每30分钟清理一次过期缓存
    this.cleanupInterval = setInterval(() => {
      this.cleanupExpiredCache();
    }, 30 * 60 * 1000);
  }

  private cleanupExpiredCache() {
    const queryClient = queryManager.getQueryClient();
    if (!queryClient) return;

    // 清理过期的查询
    queryClient.getQueryCache().findAll().forEach(query => {
      const lastUpdated = query.state.dataUpdatedAt;
      const now = Date.now();
      const age = now - lastUpdated;

      // 清理超过1小时的缓存
      if (age > 60 * 60 * 1000) {
        queryClient.removeQueries({ queryKey: query.queryKey });
      }
    });

    console.log('🧹 定期缓存清理完成');
  }

  stopPeriodicCleanup() {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = null;
    }
  }
}

3.7 状态管理最佳实践

3.7.1 状态设计原则

  1. 单一职责原则:每个 store 只负责特定领域的状态
  2. 最小化状态:只存储必要的状态,避免冗余
  3. 状态不可变性:使用不可变更新模式
  4. 类型安全:完整的 TypeScript 类型定义
  5. 性能优化:精确订阅和选择性更新

3.7.2 状态更新模式

typescript 复制代码
// 1. 基础状态更新
const updateUser = (updates: Partial<User>) => {
  const currentUser = get().user;
  if (currentUser) {
    set({ user: { ...currentUser, ...updates } });
  }
};

// 2. 复杂状态更新
const addStatistic = (stat: WorkStatistic) => set((state) => {
  const existingIndex = state.statistics.findIndex(
    s => s.indicator === stat.indicator && s.dimension === stat.dimension
  );

  let newStatistics;
  if (existingIndex >= 0) {
    // 更新现有统计
    newStatistics = [...state.statistics];
    newStatistics[existingIndex] = { ...stat, lastUpdated: dayjs().toISOString() };
  } else {
    // 添加新统计
    newStatistics = [...state.statistics, { ...stat, lastUpdated: dayjs().toISOString() }];
  }

  return {
    statistics: newStatistics,
    lastFetchTime: dayjs().toISOString(),
    error: null
  };
});

// 3. 批量状态更新
const resetAppState = () => set({
  isLoading: false,
  error: null,
  isBooted: false,
  splashLoading: true,
  hasTriedAutoLogin: false,
  lastLoginTime: null,
  lastUserId: null,
});

3.7.3 状态持久化策略

typescript 复制代码
// 1. 选择性持久化
export const useAppStore = create<AppState>()(
  persist(
    (set, get) => ({
      // store 实现
    }),
    {
      name: 'app-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 只持久化必要的状态
        theme: state.theme,
        language: state.language,
        isInstalled: state.isInstalled,
        // 不持久化敏感信息如 token
      }),
    }
  )
);

// 2. 敏感信息分离存储
export const useUserStore = create<UserState>()(
  persist(
    (set, get) => ({
      // store 实现
    }),
    {
      name: 'user-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 只持久化用户信息,token 由 SecureStore 管理
        isAuthenticated: state.isAuthenticated,
        user: state.user,
      }),
    }
  )
);

3.7.4 状态调试与监控

typescript 复制代码
// 开发环境下的状态调试
if (__DEV__) {
  // Zustand 状态调试
  useAppStore.subscribe(
    (state) => console.log('App State Changed:', state),
    (state) => state.theme
  );

  // React Query 状态调试
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        onError: (error) => {
          console.error('Query Error:', error);
        },
        onSuccess: (data) => {
          console.log('Query Success:', data);
        },
      },
    },
  });
}

// 状态监控工具
export class StateMonitor {
  static logStateChange(storeName: string, prevState: any, nextState: any) {
    if (__DEV__) {
      console.log(`[${storeName}] State Changed:`, {
        previous: prevState,
        next: nextState,
        changes: this.getStateChanges(prevState, nextState),
      });
    }
  }

  private static getStateChanges(prev: any, next: any): any {
    const changes: any = {};

    Object.keys(next).forEach(key => {
      if (prev[key] !== next[key]) {
        changes[key] = {
          from: prev[key],
          to: next[key],
        };
      }
    });

    return changes;
  }
}

3.8.1 核心架构特点

  1. 双状态管理:Zustand 管理客户端状态,React Query 管理服务端状态
  2. 类型安全:完整的 TypeScript 类型定义和类型检查
  3. 性能优化:精确订阅、缓存策略、乐观更新
  4. 错误处理:统一的错误处理机制和重试策略
  5. 状态持久化:智能的持久化策略和敏感信息保护

3.8.2 最佳实践总结

  1. 状态分类:清晰区分客户端状态和服务端状态
  2. 缓存策略:合理的缓存时间和失效策略
  3. 错误处理:统一的错误处理和用户友好的错误提示
  4. 性能优化:精确订阅、数据预取、内存管理
  5. 开发体验:完善的调试工具和状态监控

3.8.3 技术优势

  • 开发效率:类型安全的状态管理减少运行时错误
  • 用户体验:乐观更新和智能缓存提升响应速度
  • 维护性:清晰的状态架构便于功能扩展和维护
  • 性能:精确的状态订阅和缓存策略优化渲染性能

这套状态管理架构为医疗应用提供了稳定、高效、可维护的数据管理基础,确保了应用的可靠性和用户体验。


下一章预告:第四章将详细介绍组件化架构与设计系统,包括组件分层设计、NativeWind + Tailwind 样式系统、以及 Storybook 组件文档等核心内容。

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax